Posts

Posts

10 posts

AI

홈 서버에 Hermes Agent 올려봤다 — 삽질 기록

2026.06.09

요약: Ubuntu 미니PC에 AI 에이전트를 상시 실행 서비스로 올리는 게 이렇게 간단할 줄 몰랐다. 그리고 이렇게 삽질할 줄도 몰랐다. AI 에이전트 툴을 한 번 제대로 써보고 싶다는 생각은 오래 전부터 있었다. 조건은 하나였다. 에이전트는 서버에서 돌고, 나는 맥북에서 앱으로 붙는 구조. SSH 열고 터미널 띄우는 게 아니라, 앱 켜면 바로 연결되는 리모트 환경. 코딩할 때 잠깐 켜는 도구가 아니라, 서버에서 상시 돌면서 슬랙으로 명령 받고 결과 뱉는 구조도 함께 원했다. 후보는 여러 개였는데 최종적으로 둘을 비교했다. OpenClaw — GitHub 스타 수가 어마어마하고 생태계가 넓다. 심지어 Hermes에는 hermes claw migrate라는 마이그레이션 명령이 있을 정도로 인지도 있는 도구다. 근데 코드베이스가 거대하고, 내가 필요한 건 그 1%도 안 될 것 같았다. Hermes Agent — Nous Research 작. 세션 간 메모리 지속, 자가 스킬 생성, 멀티 메시징 플랫폼 지원. MIT 라이선스, 데이터는 내 머신에만. 결국 Hermes를 선택한 이유는 세 가지였다. 첫째, 맥북 전용 데스크탑 앱이 있고 리모트 서버에 붙는 구조를 공식 지원한다. Tailscale IP만 입력하면 앱에서 바로 연결된다. 둘째, Slack 연동이 네이티브로 된다. 셋째, LLM 프로바이더를 고정하지 않는다 — OpenRouter, Nous Portal, Anthropic, 자체 엔드포인트까지 hermes model 한 줄로 전환 가능하다. 집에서 운영 중인 Ubuntu 미니PC(duncan-ubuntu, Intel N100, RAM 16GB)에 올렸다. 이미 Ghost CMS, n8n이 각자 전용 유저로 격리돼서 돌고 있는 구조라, Hermes도 똑같은 패턴으로 붙이기로 했다. 접근은 Tailscale로 한다. 포트 직접 열지 않아도 된다. sudo useradd -m -s /bin/bash hermes sudo su - hermes curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash 끝이다. 진짜로. 설치 스크립트가 Python 3.11 가상환경, Node.js, 의존성 전부 알아서 세팅해준다. 2분도 안 걸렸다. 설치 후 LLM 프로바이더와 Slack 연동 설정은 두 가지 방법 중 하나로 한다. hermes setup nano ~/.hermes/.env 나는 .env를 직접 편집했다. 프로바이더 API 키와 Slack 봇 토큰을 여기에 넣으면 된다. Slack 연동 설정하고 테스트 메시지 보냈더니 바로 에러가 떴다. slacksdk.errors.SlackApiError: missingscope 원인은 Slack Bot Token 스코프 설정 누락이었다. 처음에 기본 권한만 주고 넘어갔는데, 채널/그룹 목록을 읽으려면 추가 스코프가 필요했다. Slack App 설정 → OAuth & Permissions 에서 아래 스코프를 추가하고 Bot Token 재발급 후 ~/.hermes/.env 업데이트. channels:read groups:read mpim:read im:read 이거 하나 때문에 30분 썼다. 에러 메시지는 명확했는데 어디서 추가하는지 처음엔 헷갈렸다. 처음엔 서비스가 하나인 줄 알았다. 근데 구조를 보니 둘이었다. 서비스 역할 없으면? hermes-gateway Slack/Telegram 등 연동, 실제 에이전트 루프 메시징 명령 안 먹힘 hermes-dashboard 맥북 데스크탑 앱 연결용 웹소켓 백엔드 앱 접속 불가, Slack은 정상 Slack만 쓸 거면 gateway만 올려도 된다. 나는 맥북 데스크탑 앱도 쓰고 싶어서 둘 다 systemd로 등록했다. [Unit] Description=Hermes Agent Gateway After=network-online.target Wants=network-online.target [Service] User=hermes Group=hermes ExecStart=/home/hermes/.hermes/hermes-agent/venv/bin/python -m hermescli.main gateway run WorkingDirectory=/home/hermes/.hermes EnvironmentFile=/home/hermes/.hermes/.env Restart=always RestartSec=5 [Install] WantedBy=multi-user.target [Unit] Description=Hermes Agent Dashboard After=network.target hermes-gateway.service [Service] User=hermes EnvironmentFile=/home/hermes/.hermes/.env ExecStart=/home/hermes/.local/bin/hermes dashboard --host 100.x.x.x Restart=always RestartSec=5 [Install] WantedBy=multi-user.target --host에는 Tailscale IP를 넣는다. systemctl enable 해두면 서버 재부팅 후 자동 실행된다. 한 가지 더 — gateway 서비스의 ExecStart는 반드시 venv 절대경로로 써야 한다. hermes gateway처럼 CLI 명령만 쓰면 PATH 해석이 꼬이는 경우가 있었다. 데스크탑 앱에서 Gateway 설정에 Tailscale IP와 대시보드 포트(기본 9119)를 입력하면 끝. VPN(Tailscale) 없이 외부 포트 열 필요 없다는 게 제일 마음에 드는 부분이다. 보안 신경 쓸 게 크게 줄었다. 서버에 이미 Betterstack Heartbeat 크론이 돌고 있어서, Hermes gateway용 항목을 하나 추가했다. /5 systemctl is-active --quiet hermes-gateway && curl -s https://uptime.betterstack.com/api/v1/heartbeat/[TOKEN] /dev/null systemctl is-active --quiet 조건을 걸어서, 서비스가 죽어 있으면 핑을 안 보내게 했다. Betterstack이 핑 끊기면 알람 날려주는 구조. 솔직히 말하면 아직 업무에 깊게 붙이진 못했다. 설치하고 Slack 연동되는 걸 확인하는 것까지가 이번 라운드였다. 다만 구조를 보면서 느낀 건, 이게 단순한 챗봇 래퍼가 아니라는 점이다. 세션이 끊겨도 프로젝트 맥락을 기억하고, 문제 해결 패턴을 스킬로 저장한다. 이미 내 서버에서 돌면서 스스로 스킬을 쌓아가고 있다. 지금 n8n으로 돌리는 자동화 파이프라인들을 점진적으로 이쪽으로 옮기거나 연계하는 방향을 고민 중이다. 아직 검증이 더 필요하지만, 일단 인프라 세팅 허들은 생각보다 낮았다는 건 확실히 말할 수 있다. 설치: curl 한 줄, 체감 난이도 낮음. 인스톨러가 venv·Node까지 알아서 처리 인증: LLM 프로바이더 API 키 방식 (.env 또는 hermes setup 마법사) 삽질 구간: Slack 스코프 설정, gateway/dashboard 역할 구분, systemd ExecStart 경로 운영 구조: systemd 서비스 + Tailscale + Betterstack 조합 현재 상태: 세팅 완료, 실사용 검증 중 다음엔 실제로 업무 자동화에 붙여본 결과를 써볼 예정이다. 설치 환경: Ubuntu 24.04, Hermes Agent (Nous Research), Slack Socket Mode

AIAutomation

Text-to-SQL: "매출 지난달이랑 비교해줘" 한 마디로 BigQuery의 데이터 분석 및 리포트 작성

2026.06.04

TL;DR — SQL을 몰라도 된다. 자연어로 분석을 요청하면 AI가 SQL을 자동 생성하고, BigQuery에 직접 쿼리를 날려 결과를 돌려주는 Text-to-SQL 분석 시스템을 구축했다. 핵심은 Claude Projects + BigQuery MCP + 커스텀 Analytics Skill의 조합이다. 데이터가 쌓이면 질문이 생긴다. "이번 달 채널별 매출 비중이 어떻게 돼?" "지난 3개월 평균이랑 비교하면 어때?" "해외 환자 유입이 전년 대비 얼마나 늘었어?" 이 질문들의 공통점은 답이 데이터베이스 안에 있다는 것이다. 그런데 문제가 있었다. 데이터를 꺼내려면 SQL을 알아야 한다. 우리팀은 분석 요청이 생길 때마다 업무 리소스가 발생했다. 요청 → 전달 → 쿼리 작성 → 결과 전달의 사이클이 반복됐고, 특히 복잡한 분석을 요청하는 경우 쿼리 작성 시간이 길어졌다. ​우리팀은 더 게을러지기 위해 이 Claude Project를 만들었다. Text-to-SQL은 자연어(Natural Language) 질의를 SQL로 자동 변환하는 기술이다. NL2SQL이라고도 부른다. 개념 자체는 오래됐지만, LLM(대형 언어 모델)의 등장으로 실용적인 수준에 도달했다. 특히 테이블 스키마와 비즈니스 컨텍스트를 함께 제공하면, 모델이 도메인에 맞는 정확한 SQL을 생성할 수 있게 됐다. 이 방식을 선택한 이유는 단순하다. 별도 UI 개발 없이 구현 가능 기존 BigQuery 인프라를 그대로 활용 비개발자도 즉시 사용 가능 질문의 형태와 깊이에 제한이 없음 세 가지 컴포넌트가 핵심이다. MCP(Model Context Protocol)는 AI 모델이 외부 서비스와 직접 통신할 수 있게 해주는 프로토콜이다. BigQuery MCP를 연결하면 Claude가 SQL을 생성하는 것에 그치지 않고, BigQuery에 직접 쿼리를 실행하고 결과를 받아올 수 있다. 기존 Text-to-SQL의 한계는 "SQL을 생성해주면 사람이 직접 실행해야 한다"는 것이었다. MCP는 이 단계를 없앤다. 자연어 질의부터 분석 결과까지 중간에 사람이 개입하지 않아도 된다. Claude의 Skill은 특정 도메인에 대한 지식과 규칙을 패키징한 컨텍스트 파일이다. tu-analytics Skill에는 다음이 담겨 있다. 스키마 정보 (schema.md) 테이블 구조, 컬럼 설명, 데이터 타입. Claude가 올바른 SQL을 생성하려면 테이블이 어떻게 생겼는지 알아야 한다. 지표 정의 (tables.md / 분석 지침) 단순한 컬럼 설명을 넘어, 비즈니스 지표의 정확한 계산식을 정의한다. 예를 들어 "객단가"는 단순 평균이 아니라 특정 필터 조건을 만족하는 행만 포함해야 한다. 이런 규칙이 없으면 SQL은 실행되지만 결과가 틀린다. 쿼리 패턴 (querypatterns.md) 자주 사용하는 분석 패턴을 템플릿화했다. 영업일 기준 동기 비교, 국내/해외 분리 집계, 누적 영업일 필터링 등 반복되는 로직을 표준화해두면 매번 처음부터 만들지 않아도 된다. 특수 케이스 (specialcases.md) 데이터의 예외 상황과 주의사항. 예를 들어 특정 컬럼에 NULL이 들어오는 경우 어떻게 처리해야 하는지, 중복 제거는 어떤 기준으로 하는지 등이 명시돼 있다. 이 Skill이 없으면 Claude는 일반적인 SQL은 만들 수 있지만, 이 데이터베이스의 맥락에 맞는 정확한 SQL은 만들기 어렵다. Claude Projects는 특정 프로젝트에 대한 지식과 설정을 지속적으로 유지할 수 있는 환경이다. tu-analytics Skill을 프로젝트에 연결해두면, 매번 컨텍스트를 다시 설명하지 않아도 된다. 대화가 길어지거나 세션이 바뀌어도 테이블 구조, 지표 정의, 분석 원칙이 유지된다. 질의 예시: "5월 국내/해외 매출 전월 동기 대비 비교해줘" 내부 처리 과정: 1. Claude가 Skill을 참조해 관련 테이블과 컬럼을 파악 2. "전월 동기"의 의미를 영업일 기준으로 해석 (달력 날짜 기준이 아님) 3. 국내/해외 분리 집계 쿼리 생성 (nationalitytype 기준) 4. BigQuery MCP로 쿼리 실행 5. 결과 반환 → 증감 계산 + 시각화 + 인사이트 해석 핵심은 3번과 4번 사이에 사람이 없다는 것이다. 결과 리포트 예시 "매출이 얼마야?"라는 질문은 단순해 보이지만, 어떤 컬럼을 쓰느냐에 따라 숫자가 달라진다. 고객납부액인지, 매출귀속액인지, 세금 포함/제외인지. 이 정의가 Skill에 명확하게 박혀 있어야 일관된 결과가 나온다. 수식 하나하나를 명문화하는 작업이 시스템 구축보다 더 시간이 걸렸다. "전월 대비"를 단순 날짜 수로 비교하면 안 된다. 영업일 수가 다른 달을 날짜 기준으로 비교하면 오해를 부른다. 이 시스템은 영업일 기준 동기 비교를 표준으로 정의하고, 모든 기간 비교에 이 원칙을 적용한다. 당월 / 전월 동기 / 직전 3개월 평균 / 전년 동기 — 네 가지 비교 기준을 항상 함께 제공하는 것도 규칙으로 정해뒀다. 분석 결과가 매번 다른 형태로 나오면 보는 사람이 불편하다. 스코어카드 → 차트 → 표 요약 → 인사이트의 순서를 표준화하고, 증감 표기 방식(▲/▼, 색상 코드)도 규칙으로 정의했다. 완벽하지 않다. LLM 기반 SQL 생성은 복잡한 조건이 중첩될수록 오류 가능성이 높아진다. 이 때문에 중요한 분석은 결과를 검증하는 습관이 필요하다. 컨텍스트 품질이 전부다. Skill에 정의된 스키마와 지표가 부정확하면 SQL도 틀린다. "쓰레기가 들어가면 쓰레기가 나온다(GIGO)"는 여기서도 그대로 적용된다. 초기 Skill 작성과 지속적인 업데이트가 시스템 품질을 결정한다. MCP는 현재 사용자 단위 인증이다. BigQuery MCP는 개인 인증 기반으로 동작한다. 조직 전체가 동일한 연결을 공유하는 구조가 아니므로, 팀 단위 배포 시에는 이 점을 고려해야 한다. SQL을 모르는 팀원도 분석 질문을 직접 던질 수 있게 됐다. 분석 요청 → 결과 확인의 사이클이 대폭 줄었고, "물어보기 애매해서 참던" 질문들이 올라오기 시작했다. 데이터 민주화라는 말이 있다. 데이터를 다루는 기술이 없어도 데이터에서 인사이트를 얻을 수 있게 되는 것. Text-to-SQL이 그 방향으로 한 걸음 가까워지게 해준 기술이라고 생각한다. 사용 기술: Claude Projects, BigQuery MCP, Model Context Protocol (MCP), Google BigQuery 관련 키워드: Text-to-SQL, NL2SQL, Conversational Analytics, AI Analytics, Data Democratization

AIAutomation

회사 캘린더 비서 만들기 (feat. n8n, Gemini API)

2026.05.12

잔디에 말 걸면 알아서 회의실 예약해주는 AI 비서, 직접 만들어봤습니다. 우리 회사는 회의실 예약을 Google Calendar로 관리한다. 문제는 예약할 때마다 캘린더 앱을 열고, 시간대를 확인하고, 이벤트를 직접 만들어야 한다는 것. 팀원들이 회의실 예약하러 캘린더 들어가는 걸 귀찮아하는 걸 보고 생각했다. "잔디에서 그냥 말로 하면 안 되나?" 그래서 만들었다. 잔디 채팅창에 "내일 오후 2시에 대회의실 1시간 잡아줘" 라고 보내면, AI가 알아서 캘린더에 예약을 넣어주는 시스템. \[이미지: n8n 워크플로우 전체 화면\] 사용한 스택은 단순하다. n8n — 워크플로우 자동화 허브 Google Gemini — 자연어 해석 + 도구 호출 판단 Google Calendar API — 실제 예약 실행 잔디(Jandi) — 사용자 인터페이스 (채팅) 흐름은 이렇다. 잔디 메시지 → n8n Webhook → AI Agent (Gemini) → Google Calendar → 잔디 응답 AI Agent가 중심이고, 상황에 따라 4가지 Calendar 도구 중 하나를 선택해서 실행한다. 도구 기능 createconferenceroomevent 새 회의실 예약 생성 updateconferenceroomevent 기존 예약 수정 deleteconferenceroomevent 예약 삭제 searchconferenceroom 예약 현황 조회 잔디는 특정 채널에 메시지가 오면 외부 URL로 POST 요청을 보내는 Incoming/Outgoing Webhook을 지원한다. 잔디 팀 설정에서 Outgoing Webhook을 추가한다. 채널: 봇이 응답할 채널 선택 트리거 단어: 설정하지 않으면 모든 메시지에 반응 (봇 전용 채널 추천) URL: n8n Webhook URL 입력 \[이미지: 잔디 Outgoing Webhook 설정 화면\] 잔디가 보내는 POST body 구조는 다음과 같다. json { "token": "...", "teamName": "tugether", "roomName": "회의실봇", "writerName": "홍길동", "text": "내일 오후 2시에 대회의실 1시간 잡아줘", "writer": "[email protected]" } n8n에서 잔디로 응답을 보낼 때는 잔디의 Incoming Webhook URL을 사용한다. 잔디 채널 설정 → Incoming Webhook 추가 → URL 복사. 나중에 n8n에서 HTTP Request 노드로 이 URL에 POST하면 된다. json { "body": "✅ 예약 완료! 내일 오후 2~3시 대회의실을 예약했습니다." } n8n에서 새 워크플로우를 만들고 Webhook 노드를 추가한다. HTTP Method: POST Path: 원하는 경로 (예: calendar-bot) Response Mode: Respond to Webhook 노드로 분리 팁: Response Mode를 "Last Node"로 하면 AI 처리가 끝날 때까지 HTTP 연결을 물고 있어야 한다. Gemini 응답이 느릴 수 있으니 "Respond to Webhook" 노드를 별도로 두고 먼저 잔디에 응답하는 구조를 추천한다. Webhook URL을 복사해서 잔디 Outgoing Webhook에 붙여넣으면 1단계 연결 완료. n8n에서 Google Calendar 노드를 쓰려면 OAuth2 인증이 필요하다. 1. [Google Cloud Console](https://console.cloud.google.com/?ref=haas.kr) → API & Services → Credentials 2. OAuth 2.0 클라이언트 ID 생성 (웹 애플리케이션) 3. 승인된 리디렉션 URI에 n8n OAuth 콜백 URL 추가 형식: https://[n8n도메인]/rest/oauth2-credential/callback 4. 클라이언트 ID / Secret 복사 n8n → Credentials → Google Calendar OAuth2 API → 위 값 입력 후 연결. AI Agent의 Tool 포트에 Google Calendar 노드 4개를 연결한다. 각 노드 설정: create\conference\room\event Operation: Create Calendar: 회의실 캘린더 선택 나머지 필드: AI가 채워줌 (Expression으로 설정) update\conference\room\event Operation: Update Event ID: AI가 검색해서 넘겨줌 delete\conference\room\event Operation: Delete search\conference\room Operation: Get All 시간 범위: AI가 판단해서 설정 각 노드의 이름을 도구 이름과 동일하게 지정해야 AI Agent가 올바르게 인식한다. 워크플로우의 핵심. AI Agent 노드를 추가하고 설정한다. AI Agent의 Chat Model 포트에 Google Gemini Chat Model 노드를 연결한다. Model: gemini-2.0-flash (속도/비용 균형) API Key: Google AI Studio에서 발급한 Gemini API 키 AI Agent의 System Prompt가 전체 품질을 결정한다. 아래는 실제 사용 중인 프롬프트 구조다. 너는 00(test.ai)의 전문 회의실 예약 비서야. 사용자의 입력({{ $json.body.data }})과 작성자 정보({{ $json.body.writer.email }})를 바탕으로 [예약/수정/삭제] 업무를 수행해. 1. 의도 분류: 사용자의 요청이 '신규 예약', '시간/제목 수정', '예약 취소(삭제)' 중 무엇인지 판단해. 2. 시간 계산: 현재 시간({{ $now }})을 기준으로 시작/종료 시간을 ISO8601 형식으로 변환해. (타임존: Asia/Seoul) - 별도 언급 없으면 종료 시간은 시작 시간 1시간 뒤로 설정. '2시간 동안' 등 기간 언급 시 그에 맞게 계산. 3. 제목 생성: 제목 언급이 없으면 "{{ $json.body.writer.name }}님의 회의"로 자동 생성. 4. 회의실 ID 매핑: (언급 없으면 '10층 대회의실' 기본값) - 10층 대회의실/큰방: '[email protected]' - 10층 소회의실/작은방: '[email protected]' - 8층 대회의실/미팅룸: '[email protected]' 5. 참석자: 본문의 <@이메일 멘션을 추출해 'getmemberemails'로 이메일을 확보하고 작성자({{ $json.body.writer.email }})를 포함해. - 모든 액션 전 'searchroomschedules' 도구를 호출해 해당 날짜의 전체 일정을 가져와. - [예약/수정 시]: 요청한 시간대에 겹치는 일정이 있는지 확인해. (수정의 경우 본인 기존 일정은 제외) - [수정/삭제 시]: 기존 일정 중 사용자가 말한 시간/제목과 일치하는 'Event ID'를 찾아내. 1. 신규 예약: 중복이 없을 때만 'createroomevent' 호출. 2. 일정 수정: 'Event ID'가 확인되고 변경 시간대에 중복이 없을 때 'updateroomevent' 호출. 3. 일정 삭제: 'Event ID'가 확인되면 'deleteroomevent' 호출. 모든 응답은 친절한 한국어로 하며 아래 형식을 지켜줘. ✅ 성공 시: [예약 성공 / 수정 완료 / 삭제 완료] 되었습니다. - 회의실: [회의실 이름] - 일시: [시작시간] ~ [종료시간] - 제목: [회의 제목] - 참석자: [이름/이메일 리스트] ❌ 중복/실패 시: 요청하신 [회의실 이름]은 이미 아래 일정이 잡혀 있어 처리가 불가능합니다. [해당 날짜 전체 일정 리스트] - [시작~종료] : [제목] (이후 "다른 시간으로 도와드릴까요?"라고 마무리) AI Agent의 User Message에는 잔디에서 받은 텍스트를 넘긴다. {{ $json.body.text }} 잔디 Outgoing Webhook이 text 필드에 메시지를 담아서 보내기 때문에 이렇게 받는다. AI Agent 처리가 끝나면 Respond to Webhook 노드로 잔디에 응답을 돌려준다. Response Body: AI Agent의 출력값 {{ $json.output }} 동시에 잔디 Incoming Webhook으로도 별도 POST를 보내면 채널에 메시지가 표시된다. 잔디에서 예약 요청 메시지 보내는 화면 잔디에서 AI 응답이 돌아오는 화면 실제로 이런 요청들이 동작한다. "내일 오후 3시에 소회의실 2시간 잡아줘" → 중복 확인 후 예약 생성 "이번 주 금요일 대회의실 예약 취소해줘" → 검색 후 삭제 "다음 주 월요일 오전에 어느 회의실 비어 있어?" → 가용 시간 조회 "아까 예약한 거 4시로 바꿔줘" → 이벤트 찾아서 업데이트 잔디에서 Webhook이 안 불릴 때 잔디 Outgoing Webhook은 Public URL이 필요하다. 로컬 n8n은 Cloudflare Tunnel이나 ngrok으로 외부 노출이 필요하다. Gemini가 도구를 안 쓸 때 시스템 프롬프트에 "반드시 도구를 사용하라"는 지시를 명시적으로 추가한다. Gemini는 도구 사용 여부를 자체 판단하므로 프롬프트로 유도해야 한다. 날짜 계산이 틀릴 때 시스템 프롬프트에 {{ $now }}를 포함해서 현재 시각을 명시적으로 알려줘야 한다. 이게 없으면 Gemini가 날짜를 잘못 계산하는 경우가 생긴다. 이중 예약 문제 create 전에 반드시 search로 확인하도록 프롬프트에 강제하는 게 핵심이다. Freebusy API를 별도로 연동하면 더 정확하게 처리할 수 있다. n8n + Gemini 조합으로 생각보다 빠르게 실용적인 AI 비서를 만들 수 있었다. 코드 한 줄 없이 노드 연결만으로 자연어 처리부터 Calendar API 호출까지 완성된다는 게 n8n의 강점이다. 비슷한 구조로 HR 문의 봇, 공지사항 자동 발송, 리포트 조회 봇 등으로 확장 가능하다. 잔디 대신 Slack이나 카카오워크를 Webhook으로 연결하면 그대로 이식된다. 전체 워크플로우 JSON은 추후 공유할 예정이다.

Biz

CAC와 LTV: PM이 반드시 알아야 할 단위 경제학

2026.05.10

《7가지 코드》(구글·마이크로소프트·메타 빅테크 PM 3인 공저) 中 '단위 경제학' 챕터를 읽고 정리한 내용입니다. "고객이 늘고 있는데 왜 회사는 돈을 못 버는 거지?" 이 질문에 답할 수 있는 지표가 바로 CAC와 LTV다. 단순히 사용자 수를 보는 것만으로는 비즈니스의 수익성을 제대로 파악하기 어렵다. 1달러짜리를 90센트에 팔면 누구나 많은 고객을 얻을 수 있다. 《7가지 코드》는 바로 이 함정을 피하기 위해 단위 경제학(Unit Economics)의 핵심 지표인 CAC와 LTV를 이해해야 한다고 강조한다. [Brunch](https://brunch.co.kr/@bluemarble/47?ref=haas.kr) 신규 고객 1명을 확보하는 데 드는 총비용이다. CAC = (마케팅비 + 영업비) / 신규 고객 수 예를 들어, 1억 원짜리 마케팅 캠페인으로 신규 고객 1만 명을 확보했다면 CAC = 10,000원. 신규 고객을 유치하는 데에는 마케팅비와 영업비만 드는 게 아니다. 제품을 만드는 사람에게 급여를 지급해야 하고, 제품을 실행하는 데 필요한 서버 비용을 부담해야 하며, 경우에 따라 고객지원팀도 고용해야 한다. [Brunch](https://brunch.co.kr/@bluemarble/47?ref=haas.kr) 분자뿐 아니라 분모도 마찬가지다. 분모에 어떤 고객을 포함시키느냐에 따라 CAC는 완전히 달라진다. 무료 사용자를 포함할 것인가? 1주일 만에 이탈한 고객은? 유료 전환 고객만 카운트할 것인가? CAC = (마케팅비 + 영업비 + 도구/인프라 비용) / 신규 유료 고객 수 ← 모두 동일한 기간(X개월) 기준 → 기간을 명시하고, 관련된 모든 비용을 포함시켜야 비로소 의미 있는 숫자가 된다. 한 고객이 서비스를 이용하는 동안 벌어들일 것으로 예상되는 이익의 총액이다. 여기서 핵심은 수익(Revenue)이 아닌 이익(Profit)이라는 점이다. LTV = (연간 ARPU × 매출총이익률) / 이탈률(Churn Rate) ARPU: Average Revenue Per User, 사용자당 평균 수익 매출총이익률: 매출에서 직접비용(서버비, 인건비 등)을 뺀 비율 이탈률: 연간 또는 월간 서비스 이용을 중단한 사용자 비율 연간 이탈률이 50%라면, 일반 사용자의 경우 2년간 고객으로 남아있을 것이다. 즉, 이탈률이 낮을수록 LTV는 올라간다. [Brunch](https://brunch.co.kr/@bluemarble/47?ref=haas.kr) LTV는 고객당 '한계수익'이고, CAC는 '한계비용'이며, 두 값의 차는 한계이익이다. 기업은 이 한계이익이 0보다 큰 상태를 유지해야 한다. [Brunch](https://brunch.co.kr/@bluemarble/47?ref=haas.kr) LTV : CAC 상태 해석 1 : 1 위험 겉보기엔 손익분기 같지만, CAC에 모든 비용을 포함하지 않았다면 사실상 손실 중 1.25 : 1 경고 LTV가 CAC보다 높아졌지만 마진이 너무 얇음. 즉각적인 개선 필요 3 : 1 황금 비율 ✅ 실리콘밸리의 마법의 숫자. 수익성과 성장성 균형 5+ : 1 과잉 절약 마케팅에 더 투자해야 함. 성장 기회를 놓치고 있을 가능성 LTV가 CAC보다 3배 이상 더 큰 값이 되기를 기대한다. 종종 마법의 숫자로 불리기도 하는 이 3:1 비율은 월가와 실리콘밸리 사람들의 마음속 깊이 자리하고 있다. [Brunch](https://brunch.co.kr/@bluemarble/47?ref=haas.kr) 반대로 이론적으로 5대 1의 LTV/CAC 비율은 훌륭해 보일 수 있지만, 영업과 마케팅에 자원을 최대로 활용하지 않고 있다는 것을 나타낼 수 있다. CAC에 더 많은 자금을 투자하지 않으면 큰 성장 기회를 놓칠 수 있다. [ZUZU](https://zuzu.network/resource/blog/ratio-of-ltv-cvc/?ref=haas.kr) LTV보다 CAC가 높은 상황에서는 팔면 팔수록 손해를 보게 되므로, LTV보다 높은 마케팅/세일즈 예산을 책정해서는 안 된다. [Velog](https://velog.io/@yooseungkim/Finance-Unit-Economics-%ED%95%9C-%EB%AA%85%EC%9D%98-%EA%B3%A0%EA%B0%9D%EC%9D%84-%EB%B3%B4%EB%A9%B4-%EC%A0%84%EC%B2%B4%EA%B0%80-%EB%B3%B4%EC%9D%B8%EB%8B%A4?ref=haas.kr) 목표 LTV/CAC 비율을 3:1로 설정했다면 역산이 가능하다. LTV = 100만 원 → CAC 상한선 = 약 33만 원 HubSpot은 MRR Churn이 3.5%에서 1.5%로 줄어들면서 LTV:CAC 비율이 1.7에서 4.7까지 드라마틱하게 상승했다. LTV를 높이는 가장 직접적인 레버는 바로 이탈률 감소다. [Velog](https://velog.io/@yooseungkim/Finance-Unit-Economics-%ED%95%9C-%EB%AA%85%EC%9D%98-%EA%B3%A0%EA%B0%9D%EC%9D%84-%EB%B3%B4%EB%A9%B4-%EC%A0%84%EC%B2%B4%EA%B0%80-%EB%B3%B4%EC%9D%B8%EB%8B%A4?ref=haas.kr) 업력이 짧은 스타트업은 고객의 라이프타임이 얼마나 긴지 알기 어렵기 때문에 LTV를 계산하기 어렵다. 그럴 때는 LTV 대신 투자 회수 기간(Payback Period) 개념을 활용하면 된다. [Yozm](https://yozm.wishket.com/magazine/detail/2102/?ref=haas.kr) 현금 흐름을 최적화하고자 하는 회사의 경우 12개월 이내에 CAC를 복구할 것을 권장한다. [For Entrepreneurs](https://www.forentrepreneurs.com/ko/ltv/?ref=haas.kr) 《7가지 코드》를 읽으면서 PM이 단순히 기능을 기획하는 사람이 아니라는 걸 다시 한번 느꼈다. LTV와 CAC를 제대로 이해할 때 창의적인 비즈니스 모델을 만들 수 있다. 높은 CAC를 갖고 있더라도 더 높은 LTV를 실현할 수 있는 사업 전략만 있다면 별문제가 없다. [Brunch](https://brunch.co.kr/@bluemarble/47?ref=haas.kr) 결국 PM은 제품의 수익 구조를 숫자로 이해하고, 그 숫자를 움직이는 전략을 설계하는 사람이어야 한다. CAC를 낮추고, LTV를 높이고, 그 비율을 3:1 황금 비율에 가깝게 유지하는 것. 이것이 지속 가능한 제품을 만드는 단위 경제학의 핵심이다. 참고: 《7가지 코드 — 구글·마이크로소프트·메타 빅테크 PM은 이렇게 일한다》 中 '단위 경제학' 챕터

Tools

NotePlan이란 무엇인가-2주사용 후기

2026.05.01

나의 Second Brain을 만들기 위한 수만은 검색과 공부의 결과로 결정하여 사용 중인 [NotePlan - Tasks, Notes, and Calendar](https://noteplan.co/?ref=haas.kr)이라는 앱을 소개한다. 이번 포스트에서는 대략적인 기능들만 간단히 소개하고, 추 후에 기능 하나하나 소개하는 포스트도 올리겠다. 1. 마크다운을 기반으로 해야한다. 2. [Obsidian](https://obsidian.md/?ref=haas.kr) 처럼 로컬에 .md 파일로 저장되어 관리가 되어야 한다. 3. 내가 주로 사용하는 Mac, iPad, iPhone에 연동이 되어야 한다. 4. 애플캘린더와 연동이 되어야 한다. 5. 손글씨(애플팬슬)가 되어야 한다. → 설교 노트 작성... 1. [Obsidian](https://obsidian.md/?ref=haas.kr) 노트안에서 손글씨가 불가능 캘린더 연동이 불편함 2. [Reflect Notes](https://reflect.app/?ref=haas.kr) 노트안에서 손글씨가 불가능 로컬에 .md 파일로 저장되어 관리가 안됨(only 클라우드) 3. [ProNotes](https://www.pronotes.app/?ref=haas.kr) 최후까지 고려함 캘린더 연동 기능이 없어 탈락 내가 원하는 노트 조건을 모두 충족함 NotePlan은 할 일, 노트, 캘린더를 하나로 통합하고 있는 앱이다. → 애플/구글 캘린더 + 애플 리마인더 연동 독일 개발자가 만들고 있으며, 사용자 요청을 적극 반영하고 있다. Markdown 기반으로 빠르고 간편하게 작업하고 있다. macOS, iPhone, iPad + Web에서 모두 연동하여 사용 할 수 있다. 개인적으로는 로컬에 파일을 저장 하고 이미지 업로드같은 것도 아주 편하게 알아서 설정 해준다. 예를 들면 1. 위와 같이 ~attachments라는 폴더로 같은 경로에 자동으로 생성이 된다. 2. 내가 해당 .md 파일을 이동하면 저 이미지 폴더는 자동으로 따라온다. 👏 파일 경로로 가본다 이미지를 붙여넣는다(노션에 붙여 넣는 것 처럼 편하게) 1. 어마어마한 캘린더 연동 기능 [https://youtu.be/4j9-2O44g3w](https://youtu.be/4j9-2O44g3w?ref=haas.kr) 영상에서 처럼 실시간으로 NotePlan에서 Task와 일정을 등록 하면 애플캘린더에 연동되며, 반대로 애플캘린더에 등록한 일정은 바로 NotePlan에 보여진다. 완전한 실시간 연동을 보여준다. 2. Obsidian 보다 편한 로컬 파일 관리 시스템 내가 정말 감탄한 것은 로컬 파일(.md) 관리 시스템 방식이다. ~기존에 코드에디터로 마크다운 파일을 관리하는 포멧에서 거의 벗어나지 않게 관리한다.~ 하지만 App 내에서의 사용 편의성은 너무 편하게 만들었다. 위와 같은 장점으로 GitHub Blog의 포스트를 쓰기 편하다. 3. 손글씨 가능 나는 교회에서의 설교노트는 모두 손글씨로 메모하고 아카이빙한다. NotePlan에서는 손글씨를 아주 편하게 작성 할 수 있고, 아이패드와 맥의 연동성도 최고다.(하기 \예시) 심지어 AI를 통해 손글씨를 텍스트화 할 수 있다. 4. 매우 편하고 쉬운 Templates 기능 나는 데일리노트, 설교노트, QT노트. 심지어 미팅록까지 템플릿으로 만들어 사용 중인데, 정말 편리하다 5. 노트 외부 웹 공유 노션과 같이 노트를 외부 URL로 공유 가능 하다 [NotePlan Publish](https://noteplan.co/n/7EE1A78F-6D19-4C52-B776-ED6A703AFCAE?ref=haas.kr) 6. PARA 정리법에 대한 상세한 가이드와 세팅 PARA에 대한 가이드가 상세히 정리 되어 있고, 이를 활용하여 누구나 쉽게 나의 Second Brain을 만들 수있게 해주었다. \예시 1. Web버전에서의 연동 불안성 공식적으로는 Web버전이 지원되나, 사용해봤을때 저장이 안되었던 경험이 좀 있었다. 그 이후로 불안해서 사용을 못하는데, 테스트 단계라서 그런 것 같다 2. 한글 미지원 공식적으로 한글을 지원하지 않는다. 내가 열심히 커뮤니티에 말하고 있다 3. 한국 커뮤니티 부재 한국 유저가 많이 없는 것 같고, 한국 유저의 커뮤니티가 많이 없다. 정말 괜찮은 앱이다. 아직 2주밖에 사용하지 않았지만, 사용하면서 큰 불편함이나 이슈가 없었고, 내가 사용 하는 프로그램을 줄이고 있다. NotePlan 하나로 내가 원하는 것을 다 하고 있다. 기능들을 상세하게 정리하면서 한국에 전파하겠다!

AIAutomation

n8n과 노션으로 구축하는 RAG 기반 사내 AI 챗봇 가이드

2026.03.18

자사에서는 업계 특성상 임직원의 입/퇴사가 잦은 편이고, 이로 인한 자세한 온보딩 여력이 되지 않는다. 그로인한 다양한 임직원의 문의는 인사팀의 업무 리소스로 이어지며, 이를 해결하기 위해 사내 AI 봇을 기획하고 만들게 되었다. [Make](https://www.make.com/en?ref=haas.kr) 와 [Zapier](https://zapier.com/?ref=haas.kr) 같이 다양한 애플리케이션 간의 자동화를 no code로 도와주는 서비스 이다. 하지만, 위 툴들과 다르게 [n8n](https://n8n.io/?ref=haas.kr)이 요즘 각광을 받는 이유는 한 가지다. 바로 '셀프호스팅'을 제공한다. 즉, 내가 DevOps 지식만 있다면, 거의 무료로 자동화 시스템을 구축 할 수 있다는 말이다. 처음에 기획 했을때는 RAG가 아닌 단순히 AI Agent 노드로 제작 하였다. 노션에 올라가 있는 데이터가 많지도 않고, 따라서 성능이슈는 없을 것으로 예상했다. AI Agent 독립사용 워크플로우 이미지 1. 노션 페이지의 블록 누락 이슈 이번에 정확히 알게된 사실인데, 노션 데이터를 json으로 받게 되면, 페이지에 그냥 있는 text의 depth와 콜아웃, 토글, 표 등의 블록의 depth가 달라 데이터를 읽어오는 과정에서 이를 모두 고려해야 한다. 그래서 위 이미지를 보면 "데이터베이스 Get - 각각 페이지 ID 추출 - 각 페이지 Get - 각 페이지에서 블록 추출" 하는 로직이 필요하다. 이 과정에서 난 모든 블록을 고려하는 것은 포기하고 표와 텍스트만 가져오기로 했다. 위 문제는 Notion Node를 이용하면 비교적 간단하게 해결이 될 수도 있지만 일일히 설정 해야 하는 것은 변함이 없다. 2. 성능 이슈(속도) 노션에서 데이터를 조회하는 노드가 4개가 있고, 이렇게 하여도 사용자가 채팅을 입력할 때마다 전체 노션 데이터를 실시간으로 조회해야 한다. 데이터가 적을 때는 큰 문제가 없었지만, 페이지가 늘어날수록 응답 지연이 눈에 띄게 증가했다. 매 요청마다 Notion API를 여러 번 호출하는 구조 자체가 근본적인 병목이었다. 위 두 가지 문제를 해결하기 위해 아키텍처를 완전히 재설계했다. 노션 데이터를 매 요청마다 가져오는 대신, 주기적으로 벡터 DB에 임베딩해두고 AI Agent가 이를 Tool로 검색하는 방식으로 전환했다. 전체 플로우 워크플로우는 크게 두 개의 독립적인 플로우로 나뉜다. Schedule Trigger → Get many database pages (DB1) → Get many database pages1 (DB2) → Delete Collection (Qdrant 컬렉션 초기화) → Merge (append) → Filter → Loop Over Items → HTTP Request (Notion 페이지 본문 조회) → Code in JavaScript (텍스트 가공) → Qdrant Vector Store (임베딩 저장) ← Embeddings Google Gemini ← Default Data Loader 주요 포인트: Schedule Trigger로 주기적(예: 매일 새벽)으로 실행된다. 실행 시작 시 Delete Collection으로 기존 벡터 데이터를 전부 삭제 후 재적재한다. 이렇게 하면 노션에서 수정/삭제된 내용이 자동으로 반영된다. 여러 노션 DB를 Merge (append)로 합쳐 단일 파이프라인으로 처리한다. Filter로 불필요한 페이지(미완성, 비공개 등)를 걸러낸다. Loop Over Items로 각 페이지를 순회하며 Notion API(HTTP Request)로 본문을 가져온다. Code in JavaScript로 블록 타입별 텍스트 추출 및 포맷팅을 처리한다. 최종적으로 Qdrant Vector Store에 Google Gemini 임베딩과 함께 저장한다. When chat message received → AI Agent ← Google Gemini Chat Model ← Simple Memory ← Qdrant Vector Store2 (Tool) ← Embeddings Google Gemini1 주요 포인트: 사용자 메시지를 받으면 AI Agent가 동작한다. Qdrant Vector Store를 Tool로 연결하여, Agent가 필요할 때만 벡터 검색을 수행한다. Simple Memory로 대화 히스토리를 유지해 멀티턴 대화가 가능하다. Notion API 호출이 전혀 없으므로 응답 속도가 획기적으로 빨라진다. 항목 개선 전 (AI Agent 단독) 개선 후 (RAG + Qdrant) 데이터 조회 시점 매 요청마다 실시간 주기적 사전 적재 응답 속도 느림 (Notion API 다중 호출) 빠름 (벡터 검색) 블록 누락 있음 HTTP Request로 직접 조회하여 감소 최신 데이터 반영 즉시 스케줄 주기에 따라 반영 비용 Notion API 호출 多 임베딩 비용 발생 (저렴) Notion Node 대신 HTTP Request를 사용한 이유는 유연성 때문이다. Notion API의 /blocks/{blockid}/children 엔드포인트를 직접 호출하면 블록 타입을 코드로 직접 핸들링할 수 있다. // 블록 타입별 텍스트 추출 예시 const items = $input.all(); const results = []; for (const item of items) { // HTTP Response body 추출 const body = item.json.body ?? item.json; // pageId와 pageTitle은 Loop의 현재 아이템에서 가져오기 // (Loop Over Items가 넘겨준 원본 페이지 정보) const pageId = item.json.pageId ?? item.json.id ?? body?.results?.[0]?.parent?.pageid ?? ""; const pageTitle = item.json.pageTitle ?? item.json.title ?? "Untitled"; const lastEdited = item.json.lasteditedtime ?? ""; // block results 배열 추출 const blocks = body?.results ?? []; // 각 블록에서 텍스트 추출 const textParts = []; for (const block of blocks) { const type = block.type; const blockData = block[type]; if (!blockData) continue; // richtext 배열이 있는 블록 타입들 처리 const richText = blockData.richtext ?? []; const blockText = richText .map(rt = rt.plaintext ?? rt.text?.content ?? "") .join(""); if (blockText.trim()) { // 헤딩 타입은 앞에 마크다운 표시 if (type === "heading1") textParts.push(# ${blockText}); else if (type === "heading2") textParts.push(## ${blockText}); else if (type === "heading3") textParts.push(### ${blockText}); else if (type === "bulletedlistitem") textParts.push(• ${blockText}); else if (type === "numberedlistitem") textParts.push(- ${blockText}); else textParts.push(blockText); } } const bodyText = textParts.join("\n"); results.push({ json: { text: 제목: ${pageTitle}\n\n[본문]\n${bodyText}, metadata: { pageId, pageTitle, chunkIndex: 0, source: "notion", lastEdited, } } }); } return results; 매 실행마다 컬렉션을 삭제 후 재생성하는 방식은 단순하지만 효과적이다. 업데이트/삭제 추적 로직 없이도 항상 노션과 동기화된 상태를 유지할 수 있다. 데이터 양이 많아지면 upsert 방식으로 전환을 고려할 수 있다. n8n + Notion + Qdrant + Google Gemini 조합으로 사내 AI 챗봇을 구축한 결과, 인사팀의 반복 문의 대응 부담을 크게 줄일 수 있었다. 특히 RAG 아키텍처 도입 후 응답 품질과 속도 모두 만족스러운 수준으로 개선되었다. 셀프호스팅 n8n의 가장 큰 장점은 API 키와 데이터가 외부로 나가지 않는다는 점이다. 사내 민감 정보를 다루는 HR 봇에는 이 점이 특히 중요했다.

AI

Claude로 법무법인 마케팅 콘텐츠 자동 검토 파이프라인 만들기

2026.03.11

법무법인 마케팅을 대행하는 지인의 요청으로 콘텐츠 자동 검토 파이프라인을 만들어봤다. 목표는 단순했다. 초안 하나를 넣으면 문맥, 법적 리스크, 마케팅 관점까지 순서대로 검토하고 최종 수정본을 뽑아내는 구조다. 법무법인 마케팅 콘텐츠는 일반 블로그 글처럼 "잘 읽히면 끝"이 아니다. 최소한 세 가지 기준을 동시에 통과해야 한다. | 기준 | 확인할 내용 | | --- | --- | | 가독성 | 잠재 의뢰인이 상황과 해결 방향을 쉽게 이해할 수 있는가 | | 법적 안정성 | 변호사법, 광고 규정에 어긋날 만한 표현은 없는가 | | 마케팅 효과 | CTA, 검색 키워드, Pain Point가 충분히 살아 있는가 | 문제는 이 세 가지를 한 사람이 동시에 잘 보기 어렵다는 점이다. 카피에디터는 법률 리스크를 놓치기 쉽고, 변호사는 마케팅 표현을 지나치게 보수적으로 다듬을 수 있다. 마케터는 전환율을 높이려다가 법적으로 위험한 표현을 쓸 수 있다. 그래서 검토 관점을 하나의 프롬프트에 모두 넣는 대신, 역할별 Agent로 나누기로 했다. 파이프라인은 4단계다. input/original.md - STEP 1: Context Reviewer - STEP 2: Legal Reviewer - STEP 3: Marketing Reviewer - STEP 4: Final Editor - output/step4final/final.md 각 Agent는 이전 단계의 draft.md를 입력으로 받고, 자기 역할에 해당하는 부분만 검토한다. 중요한 원칙은 하나다. 자기 역할이 아닌 영역은 건드리지 않는다. 이 원칙을 넣지 않으면 모든 Agent가 비슷한 방향으로 글을 다시 쓰기 시작한다. 그러면 단계가 나뉘어 있어도 결과는 단일 프롬프트와 크게 다르지 않다. 첫 번째 Agent는 10년 차 카피에디터 역할이다. 법률적으로 맞는지, 마케팅 성과가 좋은지는 판단하지 않는다. 오직 글의 흐름과 읽기 편한지만 본다. 주요 검토 항목은 다음과 같다. 도입, 사례, 법적 쟁점, 해결 방향, CTA의 흐름이 자연스러운가 한 문장이 지나치게 길지 않은가 문단 간 전환이 끊기지 않는가 같은 내용을 반복하고 있지 않은가 원본의 핵심 사례나 사실관계를 훼손하지 않았는가 출력은 두 파일로 나눴다. output/step1context/ ├── feedback.md └── draft.md feedback.md에는 문제점과 수정 방향을 남기고, draft.md에는 실제 수정된 본문을 저장한다. 두 번째 Agent는 법률 광고 리스크를 보는 역할이다. STEP 1에서 정리된 흐름은 최대한 유지하고, 위험한 표현만 잡는다. | 유형 | 위험한 표현 | 대체 방향 | | --- | --- | --- | | 결과 보장 | "반드시 승소", "100% 해결" | "유리한 결과를 이끌어낸 경험" | | 최상급 표현 | "국내 최고", "업계 1위" | "풍부한 경험", "다수의 유사 사건 처리" | | 비용 오해 | "무조건 전액 환급" | "사안에 따라 비용 상담 가능" | 여기서 중요한 건 모든 문장을 무조건 약하게 만들지 않는 것이다. 법적으로 문제가 될 수 있는 표현은 줄이되, 허용되는 범위 안에서는 설득력을 최대한 유지하도록 지시했다. 세 번째 Agent는 디지털 마케터 역할이다. STEP 2에서 설정된 법적 한계선을 넘지 않는 조건으로 전환율을 높이는 방향을 찾는다. 주요 검토 항목은 다음과 같다. 잠재 의뢰인의 Pain Point가 충분히 드러나는가 CTA가 구체적이고 부담스럽지 않은가 SEO 키워드가 자연스럽게 들어가 있는가 블로그, SNS, 웹사이트 중 어느 채널에 적합한 톤인가 제목과 소제목이 검색 의도에 맞는가 법리 검토 이후에는 표현이 다소 밋밋해질 수 있다. 이 단계에서는 다시 마케팅 관점에서 힘을 실어준다. 단, 결과 보장처럼 법적으로 민감한 표현은 다시 살리지 않는다. 마지막 Agent는 앞선 세 단계의 결과를 조율한다. 역할은 단순한 문장 교정이 아니다. 문맥, 법리, 마케팅 의견이 충돌할 때 최종 판단을 내리는 편집자다. 우선순위는 이렇게 정했다. | 충돌 상황 | 판단 기준 | | --- | --- | | 마케팅 효과 vs 법적 제약 | 법적 제약을 우선하고, 허용 범위 안에서 마케팅 효과를 최대화 | | 문맥 흐름 vs 마케팅 강조 | 독자 경험을 우선 | | SEO 키워드 vs 자연스러운 문장 | 자연스러운 문장을 우선 | 최종 결과물에는 본문뿐 아니라 운영에 필요한 정보도 같이 넣었다. 최종 제목 제목 후보 2~3개 단계별 주요 변경사항 추천 게재 채널 SEO 키워드 후속 콘텐츠 아이디어 Claude Code에서 바로 돌릴 수 있도록 파일 구조를 단순하게 만들었다. project/ ├── CLAUDE.md ├── agents/ │ ├── agent1context.md │ ├── agent2legal.md │ ├── agent3marketing.md │ └── agent4final.md ├── input/ │ └── original.md └── output/ ├── step1context/ │ ├── feedback.md │ └── draft.md ├── step2legal/ │ ├── feedback.md │ └── draft.md ├── step3marketing/ │ ├── feedback.md │ └── draft.md └── step4final/ └── final.md 핵심은 CLAUDE.md다. 여기에 전체 실행 순서, 각 Agent의 역할, 입출력 경로, 금지 행동을 적어둔다. Claude Code는 프로젝트 디렉토리의 CLAUDE.md를 먼저 읽기 때문에, 실행할 때마다 같은 기준으로 파이프라인을 돌릴 수 있다. 실행 요청은 이렇게 단순하게 만들었다. input/original.md 파일을 읽고 CLAUDE.md의 파이프라인대로 STEP 1부터 STEP 4까지 순차적으로 실행해줘. 각 단계마다 결과 파일을 저장하고 완료 여부를 알려줘. 이 파이프라인은 병렬 실행하면 안 된다. STEP 2는 STEP 1의 draft.md를 기준으로 검토해야 하고, STEP 3은 STEP 2에서 정리한 법적 한계선을 지켜야 한다. STEP 4는 앞선 세 단계의 결과를 모두 읽어야 한다. 겉으로는 네 명의 전문가가 독립적으로 검토하는 구조처럼 보이지만, 실제로는 순차 편집 파이프라인에 가깝다. 그래서 CLAUDE.md에 다음 원칙을 명시했다. 각 단계는 반드시 순차 실행한다. 이전 단계의 draft.md가 생성되기 전에는 다음 단계를 시작하지 않는다. 병렬 실행하지 않는다. 처음에는 하나의 프롬프트로 문맥, 법리, 마케팅 검토를 모두 시켰다. 결과는 애매했다. 법리 검토는 얕고, 마케팅 표현은 약하고, 문맥 수정은 거의 없었다. 역할을 나누니 결과가 훨씬 안정적이었다. | 항목 | 단일 프롬프트 | 4단계 파이프라인 | | --- | --- | --- | | 문맥 정리 | 부분 수정에 그침 | 글 전체 흐름 기준으로 정리 | | 법리 검토 | 위험 표현 일부만 탐지 | 표현 유형별로 체계적 검토 | | 마케팅 보강 | 일반적인 조언 위주 | CTA, SEO, Pain Point를 분리해 개선 | | 충돌 조율 | 기준이 불명확 | Final Editor가 우선순위에 따라 판단 | | 재현성 | 실행할 때마다 편차 큼 | 같은 구조로 반복 실행 가능 | 멀티에이전트 구조의 핵심은 Agent 수를 늘리는 게 아니었다. 중요한 건 역할과 금지 행동을 명확히 나누는 것이다. 특히 법무법인 마케팅처럼 표현의 강도와 리스크가 계속 충돌하는 콘텐츠는 한 번에 다 고치려 하면 결과가 흐려진다. 문맥, 법리, 마케팅, 최종 조율을 분리하니 각 단계의 판단 근거가 더 선명해졌다. Claude Code와 로컬 파일 구조만으로도 이런 검토 파이프라인은 충분히 만들 수 있다. 외부 SaaS를 붙이지 않아도 되고, 단계별 결과가 파일로 남기 때문에 나중에 어떤 판단으로 문장이 바뀌었는지도 추적하기 쉽다.

Thought

문제를 해결한다는 것에 대한 나의 고찰

2026.02.23

'문제해결' 이라는 것에 대한 내 생각을 정리한다. 인생을 살면서 많은 문제를 맞닥뜨린다. 하지만 생각보다 많은 사람들은 문제를 해결하지 못하고 포기한다는 사람들이 있고, 어려워 하는 사람들도 있다. 물론 나또한 어려운 문제들이 있고, 결국 해결하지 못하는 경우들이 많다. 하지만 내가 이 글을 쓰는 이유는 많은 사람이 문제를 해결하겠다면서 진짜 문제가 뭔지도 모르고 쉬운 해결방법을 두고 어렵게 해결하려는 사람들이 있어 이 글을 쓴다. 22년도 퇴사 후 SW사관학교정글에서 공부를 하고 있을때, 하루에 해결해야 할 문제들이 10개는 넘었었다. 왜 지금 코드에서 오류가 나는지, 왜 이렇게 시스템이 동작 하는지, 무슨 알고리즘을 요기에 써야 하는지 등등... 어느날 우리는 가상메모리 공부를 하면서 사람 3명이서 동일한 문제를 맞닥뜨리고 고민하고 있었다. 그때 때마침 코치님께서 오셨고, 우린 차분하게 문제를 설명해 드리며 도움을 구했다. 하지만 코치님은 우리의 질문에는 답하지 않으시고, 우리가 갖고 있는 문제들에 대해서 되묻기만 하셨다. 왜 이것을 문제라고 생각했나? 그렇게 생각한 이유는 무엇인가? 그게 문제라고 생각 했다면 그 문제가 발생한 이유가 무엇인가? 그 문제가 발생한 이유가 어떤 것이라고 생각 하나? 이런 대화를 하면서 우리는 계속해서 그 질문에 답하기 위해 생각해야 했다. 계단을 하나씩 하나씩 올라가면서 조금씩 멀리있는 건물을 보게 되는 것 같았다. 💡 이렇게 대답을 해가면서 결국 그 문제의 해답은 우리의 입에서 나왔다. 정말로 우리는 문제를 알고 있었을까? 가끔 지인들이나, 친구, 동생들에게 고민이 있다고 말하며 상담을 요청 하는 경우들이 있다. "정말 답이 없는 문제에요." "1주일은 생각해 봤는데, 해결 방법이 없어요" 하는 고민들을 들어보면, 90%는 정말 문제가 뭔지를 파악하지 못하는 경우들이다. 진짜 문제는 다른곳에 있는데, 엉뚱한 곳에서 해결점을 찾으려고 하니 해결을 못하는 것이 당연한 것 아닌가? 나는 SW사관학교정글에서의 경험으로 내린 생각은. 💡 가장 중요하고 또한 어려운 것은 "진짜 문제를 찾고 그 원인을 찾는것 이라고 생각하며, 해결하는 방법은 너무나도 쉽다." 이다. 내가 경험했던 SW사관학교정글에서의 경험을 다시 생각해보자 코치님은 우리의 문제를 해결하는 가장 간단한 방법으로 "답을 바로 말해 주는것" 이였을 것이다. 하지만 코치님은 그렇게 하지 않으시고, 우리가 스스로 답을 찾을 수 있도록 도와 주셨다. 질문을 하셨다. 내가 주목하는 점은 이것이다. 나의 문제에서 궁금한 점이 나오지 않을 때까지 질문 하는 것이다. 1. 지금 내가 생각하는 문제에게 질문한다. 2. 질문은 내가 더이상 질문을 할 수 없을 때까지 질문한다. 3. 더이상 질문을 할 수 없다면, 각각의 질문들에 대한 해결방법을 다시 고민 한다. 4. 운이 좋다면, 가장 마지막에 한 질문의 문제만 해결 되었을때, 모든 문제가 해결되는 마법을 경험한다. 이것이 내가 생각하는 문제 찾고 해결에 가까워 지는 방법이다. 어떤 회사의 엘레베이터가 너무 느려서 직원들의 불만이 사장에게 까지 닿게 됐다. 사장은 두 직원들에게 당장 이 문제를 해결할 수 있는 방안을 들고 오라고 하였다. A직원은 몇천만원을 들여 엘레베이터를 업그레이드 하여 속도를 높히는 작업을 하겠다고 하며, 엘레베이터 업체 리스트와 견적서, 업체 비교분석까지 완벽하게 해와서 사장에게 제출했다. B직원의 사장에게 1줄짜리 보고서만 제출했다. 그 보고서에서 "엘레베이터 안밖으로 거울과 광고 게시판을 설치하면 됩니다." 였다. 이처럼 "엘레베이터가 느리다" 라는 문제를 해결하는 방법은 단순히 "엘레베이터의 속도를 높힌다"로 생각 하기 쉽지만, 사장은 당연히 B직원의 아이디어로 문제를 해결 할 것이다. 문제를 해결 할 수 있는 방법은 수만가지다. 중요한건 명확한 페인포인트를 해결하면서 효율을 생각하는 것. 그것이 PM의 역할이다. 직장에서나 인생에서나 우가 풀어야 하는 문제들은 절대 단순하지 않다. 하지만 숲이 불타고 있는데, 지금 내눈 앞에 나무 한그루의 불만을 끄기 위해서 노력한다면, 너무 바보같지 않은가? 내가 위에 말한 나의 문제 해결 방법은 "숲을 보는 방법"이다. 아무리 복잡해 보이는 문제여도 하나하나 의문을 갖고 계단을 올라가다 보면, 결국은 원인을 발견 하고 해결 할 수 있다고 나는 감히 확신한다.

Tools

Obsidian vs NotePlan vs Amplenote

2026.01.05

디지털 노트 앱 시장은 이제 단순 기록을 넘어 '지능형 워크플로우'의 시대로 접어들었습니다. 로컬 기반의 자유도 끝판왕 Obsidian, 캘린더 중심의 완벽한 플래너 NotePlan, 그리고 태스크 실행에 최적화된 Amplenote를 전격 비교합니다. 구분 Obsidian (옵시디언) NotePlan (노트플랜) Amplenote (앰플노트) 철학 개인 지식 베이스 (PKM) 구축 데일리 플래닝 + 할 일 관리 아이디어 실행 깔때기 (Capture to Schedule) 데이터 저장 로컬 기반 (Markdown 파일) 로컬 + iCloud (애플 특화) 클라우드 기반 (E2E 암호화) 핵심 기능 그래프 뷰, 캔버스, 무한 플러그인 데일리 노트-캘린더-태스크 통합 Task Score(우선순위), Rich Footnotes 2026 이슈 'Bases' 기능으로 데이터베이스 강화 v3.12 이후 손글씨 및 AI 인식 강화 AI 자동 요약 및 캘린더 양방향 동기화 고도화 무용 범위: 개인용은 완전 무료. 노트 개수 제한 없음. 플러그인, 코어 기능 모두 사용 가능. 유료 범위: \ Sync ($4~5/월): 기기 간 암호화 동기화. Publish ($8~10/월): 노트를 웹사이트로 발행. Commercial ($0): 2025년부터 상업용 라이선스가 대부분 무료화되어 기업에서도 비용 부담 없이 사용 가능. 특징: 비용보다는 '구축 시간'이 비용인 앱입니다. 무료 범위: 공식적인 무료 플랜 없음 (7일~14일 무료 트라이얼 제공). 유료 범위: \ Personal (연 약 $99 / 월 $12): 모든 기기(Mac, iOS) 사용 및 동기화. 특징: 구독형 모델이지만, 애플 생태계(iCloud, Reminders)와의 완벽한 연동으로 비용 지불 가치가 높다는 평이 많습니다. 무료 범위: Personal 플랜 무료. 무제한 기기 동기화, 기본 4개 모드(Jots, Notes, Tasks, Calendar) 제공. 유료 범위: \ Pro ($5.84/월): 외부 캘린더(Google, Outlook) 양방향 동기화. Unlimited ($10/월): 보안 금고(Vault), 노트 웹 발행. 특징: '개인 사용'에 가장 너그러운 무료 범위를 제공하며, 외부 서비스 연동 시 비용이 발생합니다. Obsidian: 기본 기능: 내장 캘린더 뷰가 없으며, 'Daily Notes' 코어 플러그인만 제공. 확장성: 'Calendar', 'Full Calendar', 'Prisma Calendar' 플러그인을 설치해야 함. 최근 'Bases' 기능 업데이트로 날짜 기반의 데이터베이스 뷰(Calendar View)를 실험적으로 도입 중. NotePlan: 독보적 강점: 앱 자체가 캘린더입니다. 우측에 항상 캘린더가 위치하며, 노트를 날짜에 바로 연결합니다. 타임 블로킹: 할 일을 캘린더로 드래그하여 바로 일정화하는 기능이 매우 강력하며, Apple/Google 캘린더 일정과 내 할 일을 한 화면에서 겹쳐 볼 수 있습니다. Amplenote: 드래그 앤 드롭: 'Tasks' 모드에 쌓인 할 일을 'Calendar' 모드로 끌어다 놓는 방식. 양방향 동기화: 유료 플랜 시 외부 캘린더와 실시간 양방향 동기화가 되어, Amplenote에서 시간을 조정하면 Google 캘린더에도 즉시 반영됩니다. Obsidian: 네이티브 지원 부족: 기본적으로 텍스트 중심입니다. 플러그인 의존: Excalidraw 플러그인을 통해 손글씨를 그리거나, Handwritten Notes 플러그인으로 PDF 위에 필기하는 방식을 사용합니다. 개인적으로 난 [Ink](https://github.com/daledesilva/obsidianink?ref=haas.kr) 라는 플러그인을 아주 잘 사용 하고 있다. NotePlan: 최고의 손글씨 지원: v3.12 업데이트로 Apple Pencil을 활용한 네이티브 드로잉 기능을 탑재했습니다. AI 변환: 손으로 그린 동그라미를 체크박스로 인식하거나, 손글씨 텍스트를 AI가 즉시 디지털 텍스트로 변환(OCR)해 줍니다. 아이패드 사용자에게 가장 추천하는 앱입니다. Amplenote: 부분 지원: 아이패드 앱에서 기본 드로잉 기능을 지원하며, Excalidraw가 내장되어 화이트보드 형태의 작업이 가능합니다. 다만 NotePlan만큼의 유연한 필기-텍스트 혼합 능력은 다소 부족합니다. 1. "나는 내 지식 세계를 무한히 확장하고 싶다" → Obsidian (무료, 커스터마이징, 로컬 보안) 2. "나는 아이패드와 아이폰을 쓰며 오늘 하루 일정을 완벽히 관리하고 싶다" → NotePlan (애플 연동, 최고의 손글씨 지원, 캘린더 일체형) 3. "나는 아이디어를 메모하고 우선순위에 따라 바로 실행하는 게 중요하다" → Amplenote (태스크 스코어링, 외부 캘린더 양방향 연동)

Tools

Notion Mail(노션메일) 사용 해보기

2025.03.27

하기 링크를 통해 신청이 가능하다. 나는 한 1개월 정도 걸렸던 것 같다. [Notion Mail - Join the waitlist](https://www.notion.com/ko/product/mail?ref=haas.kr) 이제 그냥 사용 가능 하다 👍 아래와 같이 메일이 오고, Get Notion Mail free 버튼을 누르면 시착 페이지로 간다. 내용을 보면, 1. 메일 필터를 내 입맛에 맞게 설정하여 보고 2. 노션 AI와 노션 글 서식을 사용 할 수 있고 AI가 메일을 자동 라벨링 해준다 라고 자랑을 하고 있다. 사용해 보자 기본적으로는 Gmail를 연동해서 사용해야 한다. 연동 시 구글 계정을 통해 Gmail, Calendar를 연동 하라고 한다. 난 둘다 연동 했다. 추가로 노션 캘린더 처럼 별도의 App으로 만들었다. 1. Views 1. 인박스와 라벨 구분으로 되어 있다. 2. 카테고리도 있는데, 메일의 라벨 기능을 이용한 기능이다. 2. Mail 1. 기본적은 메일 보관함과 보낸 메일함이 있다. 3. 세팅 1. 기본적인 세팅 메뉴가 있다.(언어 변경은 없다) 4. 탬플릿 1. 탬플릿은 Views에 대한 탬플릿이 있다. 2. 다양한 필터값을 세팅해 두었다 5. 푸터 1. 노션, 노션캘린더로 바로가는 버튼이 있다. 라벨, 필터 기타 구분값을 설정 할 수 있는 메뉴가 있다. 재밌는 건 Group by 에 Priority 라는 구분 항목이 있고, 이를 사용 하면 사이드 바에 하기와 같이 나타난다. 뷰 화면에서는 다음과 같이 보인다 이처럼 메일의 상태값을 분리 하여 관리 할 수 있다. 신기한건, Gmail로 가보면, 아래와 같이 라벨이 만들어져 있다. 메일의 라벨 시스템을 극한까지 활용한 모습이다. 메일 에디터에서 노션 문법을 거의 그대로 사용 할 수 있다. (TODO는 아직 구현이 안된 것 같다...) 캘린더도 연동을 하는데, 이부분은 나중에 디테일하게 활용 해보고 정리하겠다. 캘린더에서 메일까지 만든거 보면, 노션의 방향성이 대략적으로 보이는 것 같다. 한가지 좀 아쉬웠던건 메일이 구글 메일에만 제약되어 사용이 되는 것 같아서 아쉬웠다. 아마 장기적으로는 노션 자체 서버를 활용한 이메일이 나올 것 같은데, 그러면 좀더 재밌겠다. 우선 난 회사메일을 Gmail에 연동 하고, 다시 Gmail을 노션 메일에 연동했는데, 진정한 아웃룩(?)처럼 사용 하려면, 노션메일 자체적으로 POP을 지원해야 할 것 같다. 노션 화이팅.

Posts