AI 실험실

하네스 엔지니어링 — 개발자도 아닌 교사가 어떻게 AI에게 작업 환경을 설계해 주고 많은 프로젝트들을 만들어가고 있을까?

프롬프트보다 환경 — 추구하며 노력해 온 25개 사례

홍창욱Apr 28, 2026
하네스 엔지니어링AI 작업환경교육 AI바이브 코딩교사 빌더
AI 실험실

하네스 엔지니어링 — 개발자도 아닌 교사가 어떻게 AI에게 작업 환경을 설계해 주고 많은 프로젝트들을 만들어가고 있을까?

DoRm이 교육 현장 문제를 어떻게 기술과 실험으로 풀어가는지 기록합니다.

안녕하세요. 저는 개발자가 아닌 교사입니다.

수업도 하고, 담임도 하고, 행정 업무도 합니다. 그런데 어쩌다 보니 작년부터 학교 현장에서 쓰일 수 있는 도구를 직접 만드는 일에도 시간을 쓰고 있습니다. 학생 관찰 기록을 생기부 문장으로 바꿔주는 살핌, 기숙사 운영 전체를 한 플랫폼에 담은 주도, 함수식을 입력해 포탄 궤적을 그리는 앵그리매스, 학생들이 직접 모의고사를 만드는 북곽원, 교내 앱스토어 북깍스토어, NEIS 추정분할점수 계산기, 그리고 담임 업무 자체를 자동화하는 OneDrive 기반 시스템까지. 모든 코드는 AI와 함께 씁니다. 정확히 말하면, AI에게 시키고 제가 검수합니다.

요즘 안드레 카파시(Andrej Karpathy)가 던진 한 줄짜리 아이디어가 화제입니다. 2026년 4월에 그가 트윗으로 공유한 "LLM Knowledge Bases", 이른바 LLM wiki입니다. 자신의 메모와 문서를 평범한 마크다운으로만 정리해 두면, 그 폴더 자체가 LLM이 읽고 답하는 지식 베이스가 된다는 발상입니다. 외부 문서를 매번 검색해 가져오는 식의 거창한 시스템(흔히 RAG라 부릅니다) 없이도, 한 답변이 다음 답변에 누적되어 지식이 자라납니다. 30분이면 본인만의 LLM wiki를 만들 수 있을 만큼 단순한 도구인데, 그 단순함이 오히려 큰 반향을 만들었습니다.

카파시가 굳이 이 아이디어를 던진 이유는 분명합니다. AI에게는 컨텍스트 제한이 있고, 한 번에 너무 많은 정보를 들이밀면 핵심을 놓치거나 엉뚱한 방향으로 일합니다. 그러니 AI에게 깔아 줄 작업 환경, 즉 하네스는 간단해야 합니다. 사람도 똑같습니다. 업무 체계가 복잡하고 지시사항이 많으면 누락이 생기고, 알아듣기 어렵고, 결국 일이 안 굴러갑니다. 카파시가 마크다운 폴더 하나라는 가장 단순한 형태를 고른 건 그래서입니다. 단순함이 미덕이 아니라 단순함이 작동의 조건이라는 뜻입니다.

DoRm.dev의 지식 베이스(kb_projects/kb_nodes/kb_blog_links)도 사실 카파시의 이 발상을 모방해서 만들었습니다. 단순한 그릇에 천천히 누적되도록, 같은 방식의 구조를 한국 학교 도메인 위에 얹어 둔 셈입니다. 여전히 완벽하지 않고 매주 부족한 점을 발견합니다. 다만 그 그릇이 점점 차오르는 과정에서 AI에게 일을 더 잘 시키기 위해 깔아 온 작업 환경 조각들이 누군가에게 도움이 될지 모른다는 생각으로 글을 씁니다.

조금 다른 이야기를 잠시 하고 가겠습니다. 제 개인적인 생각입니다만, 학교에서 오래 써 온 "인재"라는 단어의 의미가 빠르게 변하고 있다고 느낍니다. 시키는 일을 가장 잘 해내는 존재가 이제는 AI이기 때문입니다. 그러면 사람의 자리는 어디인가요. 일을 시킬 줄 아는 자리, 즉 사장의 자리입니다. 모두가 AI에게 일을 제대로 시킬 줄 아는 사장이 되어야 한다, 이게 지금 시대의 인재 정의로 옮겨가고 있고, 그게 곧 하네스 엔지니어링이 묻고 있는 질문이기도 합니다. 그렇다면 시키는 일을 잘 해내는 학생이 곧 훌륭한 학생이라고 가르치고 강조해 온 학교도 이제는 함께 변해야 한다고 생각합니다. 이 역량을 학생들에게 길러 주는 일은 우리 교사의 몫이라고 생각합니다. 동료 교사 분들께서 이 흐름을 같이 봐 주셨으면 좋겠고, 이 글이 그 시작점에 작은 참고가 되기를 바랍니다.

이 글이 다루려는 키워드가 하네스 엔지니어링(Harness Engineering)입니다. 정확하지 않게 표현하면 "AI에게 작업장을 만들어 주는 일"이고, 정확하게 표현하면 AI 에이전트가 혼자서도 일관되게 좋은 결과를 내도록 작업 환경 자체를 설계하는 기술입니다. 개발자도 아닌 평범한 교사인 저는 여전히 AI와 함께 밤새도록 투닥거리(?)를 하면서 성장 중입니다. 본 글은 그 과정에서 부족하게나마 제가 구현해 온 하네스 엔지니어링의 사례들을 모아 본 것입니다.

1. 하네스 엔지니어링이라는 개념

먼저 짧게 개념을 정리하고 넘어가겠습니다.

요즘 가장 자주 인용되는 장면 하나로 시작하겠습니다. 최근 OpenAI가 자사 실험 결과를 공개했는데, 5개월 동안 만든 한 묶음의 프로덕션 소프트웨어 코드가 100만 줄이 넘습니다. 그런데 그 가운데 사람이 직접 친 줄은 단 0줄입니다. 엔지니어 3~7명이 5개월 동안 실제로 한 일은 코딩이 아니라 AI 에이전트가 잘 일할 환경을 만들어 주는 일이었습니다. 이 환경을 부르는 이름이 바로 하네스(harness)이고, 그것을 설계하는 기술이 하네스 엔지니어링입니다.

AI를 잘 쓰는 기술의 진화는 대체로 세 단계로 이야기됩니다. 1단계는 프롬프트 엔지니어링입니다. "이렇게 말해 봐"라는 식으로 질문을 잘 다듬는 단계입니다. 2단계는 컨텍스트 엔지니어링입니다. "이런 배경지식도 같이 줄게"라는 식으로 맥락을 풍부하게 하는 단계입니다. 3단계가 하네스 엔지니어링입니다. "환경 자체를 만들어 줄 테니, 너는 그 안에서 일해"라는 식으로 작업 환경을 통째로 설계해 주는 단계입니다.

왜 환경까지 가야 하는가. 솔직히 말하면, 프롬프팅은 이제 인간이 가장 잘하는 영역이 아닙니다. 프롬프트도 결국 AI가 AI한테 시키는 게 더 잘 됩니다. 메인 에이전트가 서브 에이전트에게 던지는 프롬프트를 사람이 한 자 한 자 다듬을 일은 점점 줄어듭니다. 컨텍스트 또한 자동 축적이 잘 되는 영역입니다. 그러면 인간이 마지막까지 챙겨야 하는 영역은 어디인가. 그게 바로 작업 환경, 즉 하네스입니다. 어떤 폴더 구조에서, 어떤 규칙 아래에서, 어떤 검증 게이트를 통과해서, 어떤 도구로 일할 것인가. 이건 사람이 정해 줘야 합니다.

그런데 왜 하필 2026년에 이 단어가 이렇게 주목받게 됐을까요. 크게 세 가지가 겹쳤다고 봅니다.

첫째, AI 모델이 상향 평준화 됐습니다. Claude·GPT·Gemini 같은 주요 모델들의 벤치마크 점수가 빠르게 좁혀졌고, 더는 "어떤 모델을 쓰느냐"가 결정적인 차이를 만들지 못하는 시기가 왔습니다. 그러면 무엇이 차이를 만드는가. 모델을 둘러싼 시스템, 즉 하네스입니다. 실제로 LangChain은 같은 모델을 쓰면서 하네스만 개선해 코딩 벤치마크 점수를 52.8%에서 66.5%로 올렸습니다. 32위 밖에 있던 순위가 Top 5 안으로 들어왔습니다. 모델은 그대로인데도 말입니다.

둘째, AI 에이전트가 데모를 벗어나 실무로 들어왔습니다. 2025년까지는 "AI가 코드를 짜네, 신기하다" 정도의 반응이 많았는데, 2026년에는 실제 프로덕션 환경에서 에이전트가 돌기 시작했습니다. 그러자 짧은 작업은 잘하지만 긴 작업에서 궤도를 자주 이탈하고, 자기가 만든 결과를 평가시키면 늘 잘했다고 답하고, 같은 실수를 반복하며 무한 루프에 빠지는 문제가 본격적으로 드러났습니다. 이런 문제는 프롬프트 한 줄을 더 잘 다듬는 것만으로는 해결되지 않습니다. 결국 구조적 해결책, 즉 하네스가 필요해졌습니다.

셋째, 이 흐름에 드디어 이름이 붙었습니다. 2026년 2월, HashiCorp 공동 창업자이자 Terraform을 만든 Mitchell Hashimoto가 자기 블로그에 이 개념을 정리하면서 "Harness Engineering"이라는 이름을 붙였습니다. 핵심 철학은 짧고 분명합니다. 에이전트가 실수할 때마다 "다음에 잘하라"고 말하지 말고, 그 실수가 구조적으로 재발하지 않도록 시스템을 엔지니어링하라. 이 글이 나오고 며칠 뒤, 위에서 본 OpenAI의 100만 줄 코드 실험이 "Harness Engineering"이라는 제목으로 발표되면서 이 용어가 업계 전체로 빠르게 퍼졌습니다.

하네스는 보통 여섯 개의 축으로 이야기됩니다.

  • 구조(Scaffolding): 폴더링과 도구 배치, 경계 설정. 한 번 해 두면 계속 효력이 있는 것.
  • 맥락(Context): AI가 무엇을 알고 일하는가. 무엇을 어떻게 보여 주는가.
  • 계획(Planning): 일을 시키기 전에 무엇을 할지 함께 정하기.
  • 실행(Orchestration): 어떻게 시키는가. 혼자 시킬지, 부하를 파견할지, 팀을 꾸릴지.
  • 검증(Verification): 결과물을 어떻게 믿을 것인가.
  • 개선(Compound): 어떻게 계속 나아질 것인가.

이 글에서는 위 여섯 축마다 우리가 실제로 깔아 둔 사례들을 소개합니다. 다시 말씀드리지만 우리가 완벽하다는 뜻은 아닙니다. 어떤 프로젝트에는 빌드 하네스라고 부를 만한 게 거의 없습니다(솔직히 인정하겠습니다). 어떤 프로젝트에서는 한 번의 시행착오가 운 좋게 좋은 인프라로 굳었습니다. 이 글에서 공유하고 싶은 건 그 추구의 흔적입니다.

핵심만 미리 적어 두자면 이렇습니다. 좋은 하네스는 점점 단순해집니다. 검증은 가장 깊은 층에 박을수록 안전합니다. AI가 AI한테 일을 시키는 시대에는, 교사도 자기 작업 환경을 설계할 줄 아는 사람이 됩니다.

한 가지만 안내드립니다. 아래 사례들은 각 축 안에서 대체로 제가 그 프로젝트를 처음 시작한 시점 순서로 배치했습니다. 가장 일찍 시작한 주도가 2025년 11월, 가장 늦게 시작한 앵그리매스가 2026년 4월입니다. 그 사이에 시행착오가 쌓이면서 다음 프로젝트의 하네스가 조금씩 더 정교해졌으니, 같은 축 안에서도 뒤로 갈수록 한 단계씩 다듬어진 형태가 보일 겁니다. 다만 이야기가 더 자연스럽게 이어지는 자리에서는 시간순을 살짝 비켜 두기도 했으니 그 점을 감안하면서 읽어 주시면 좋겠습니다.

오늘은 2026년 4월 29일입니다. 이 글에 담은 사례 너머로 앞으로도 새로운 하네스가 계속 쌓일 거라고 생각합니다(그렇게 되기를 바라고요). 기회가 되면 그 다음 사례들은 Part 2로 묶어 한 번 더 글로 게시해 보려고 합니다.

잠깐, 사례를 읽기 전에 — 용어 여섯 가지만 정리

아래 사례들에서 같은 단어들이 계속 등장합니다. 한 번 비유와 위치를 익혀 두면 읽기가 한결 가벼워집니다. 지금까지 우리가 만든 것들이 어디에 어떤 모양으로 저장되어 있는지를 함께 정리한 표라고 봐 주시면 됩니다.

스킬(Skill)서브에이전트(Subagent)훅(Hook)플러그인(Plugin)
비유매뉴얼전문 직원법률확장팩
프로젝트 위치.claude/skills/.claude/agents/.claude/settings.json마켓플레이스
전역 위치~/.claude/skills/~/.claude/agents/~/.claude/settings.json동일
실행 보장❌ Claude 판단❌ Claude 위임✅ 100% 자동구성 따라
우리 실제 예시(전역) /loop, /보안성검토-하·중·상, ui-ux-pro-max / (프로젝트) /cross-post(전역) codex:rescue (Codex에 위임), claude-code-guide (메인이 옆 Claude에 위임)(전역) dev-loop-edit-guard.sh, pre-agent-routing-check.sh, session-start.sh, stop-session-protocol.shsuperpowers, hookify, codex, frontend-design

비유로 한 번 더 정리하면 이렇습니다.

  • 전역 (~/.claude/) = 회사 전체 규칙. 내가 하는 모든 프로젝트에 적용됩니다.
  • 프로젝트 (.claude/) = 이 팀만의 규칙. 지금 이 프로젝트에만 적용됩니다.

같은 종류의 파일을 두 자리에 둘 수 있는데, 위치만 따로 정리하면 다음 표가 됩니다.

파일전역 위치프로젝트 위치
CLAUDE.md~/.claude/CLAUDE.md./CLAUDE.md
스킬~/.claude/skills/.claude/skills/
서브에이전트~/.claude/agents/.claude/agents/
훅 설정~/.claude/settings.json.claude/settings.json

여기에 더해 AI가 자기 바깥의 도구나 시스템과 연결되는 방식이 두 가지 있습니다. MCP와 CLI입니다. 이 둘의 차이를 알아 두면 토큰 비용을 줄이는 데 큰 도움이 됩니다.

항목MCP (Model Context Protocol)CLI (Command Line Interface)
비유항상 대기 중인 통역사필요할 때 보내는 심부름꾼
연결 방식별도 서버가 떠 있고 도구 목록을 항상 들고 있음명령 한 줄을 그때그때 터미널에 실행
컨텍스트 부담도구 정의가 매 세션 컨텍스트에 올라감 → 토큰 비용 ↑명령 결과만 받아옴 → 토큰 가벼움
잘 맞는 작업양방향·실시간, 외부 SaaS 연동, 코드 심볼 탐색단순 명령, 파일 조작, 다른 모델 호출
최근 트렌드무거워서 점점 골라 쓰는 추세가볍고 직관적이라 늘어나는 추세
우리 실제 예시Supabase(DB DDL/DML — list_tables/apply_migration/execute_sql/get_advisors가 한 세트), Serena(심볼·메모리), Context7(라이브러리 문서), Playwright/Chrome DevTools(E2E), Sequential-thinkingslack(담임 자동화 — 학생·교사 워크스페이스 메시지·90일 백업·pin 교체), vercel(블로그 배포), gh(GitHub 이슈·PR·릴리즈), codex/gemini(보안 검토에서 다른 모델 호출), pnpm(빌드·테스트)

같은 도구를 두고도 우리는 서로 다른 방식을 고릅니다. 이유는 각 작업의 결이 달라서입니다. Supabase는 스키마 조회 → 마이그레이션 작성 → 적용 → advisor 점검처럼 양방향이 많고 도구 세트가 한 묶음으로 움직이는 작업이라 MCP가 이득입니다. list_tables, apply_migration, execute_sql, get_advisors 같은 도구가 한 번에 컨텍스트에 올라와 한 세션 안에서 풍부하게 들여다볼 수 있기 때문입니다. 반대로 Vercel 배포는 보통 vercel deploy 한 줄이면 끝이고, 결과로 받는 것도 URL과 상태 정도입니다. 매 세션에 도구 정의를 들이지 않아도 되니 CLI가 자연스럽고 가볍습니다. Slack도 마찬가지입니다. 담임 자동화에서 우리가 실제로 쓰는 흐름은 "메시지 작성 → 사람 검수(AskUserQuestion) → slack chat send 한 번"인데, 이런 단방향 발송에는 CLI가 잘 맞습니다. 검색·필터·반응 추가 같은 양방향이 자주 필요해진다면 그때 MCP로 옮겨도 됩니다.

여기서 한 가지를 짚어 두자면, 위에 적은 CLI 도구들(slack, vercel, gh, codex, gemini, pnpm 등)은 모두 시스템 PATH(~/.local/bin/, ~/.npm-global/bin/)에 전역으로 한 번 깔려 있고, MCP 서버 또한 Claude Code 전역 설정에 등록해 둡니다. 한 번 깔아 두면 모든 프로젝트에서 같은 도구가 손에 잡히기 때문에, 같은 도구를 매번 다시 세팅할 일이 없습니다. 위의 용어 표 첫 번째 행이 "전역 vs 프로젝트"였던 이유와 같은 결입니다. 도구가 손에 자주 잡히려면 가급적 전역에, 이 프로젝트만의 약속이 있으면 그때 프로젝트에 두면 됩니다.

이 여섯 단어가 깔리는 자리(전역인지 프로젝트인지), 누가 발동시키는지(내가 부르거나, AI가 알아서 부르거나, 자동으로 끼어들거나), 그리고 어떤 방식으로 외부와 연결되는지(MCP인지 CLI인지)만 구분해 두면, 아래 사례들이 어디에 어떻게 누적되어 있는지가 한눈에 보입니다.

2. 우리의 사례 — 6개 축으로 본 의도 있는 하네스들

2.1 구조(Scaffolding) — 한 번 깔면 계속 효력이 있는 것

Progressive Disclosure 온보딩 + Serena 라우팅 — 살핌 (2026-01)

살핌CLAUDE.md는 약 50KB짜리 거대한 문서입니다. 도메인 규칙, 권한 매트릭스, 코딩 컨벤션, 마이그레이션 절차, 배포 체크리스트, 분류 체계까지 다 들어 있습니다. 처음에는 이걸 매 세션 처음에 통째로 읽도록 두었습니다. 그러자 컨텍스트가 빠르게 차고, 정작 작업할 때는 50KB 중 5%만 필요한데 95%가 잡음으로 남는 일이 잦았습니다.

지금은 CLAUDE.md의 첫 부분에 상황별 라우팅 표를 두고, 각 작업 카테고리(보고서 작성, 생기부 가이드, 마켓 리서치, DB 분석, 디자인)마다 어느 문서를 언제 읽을지를 명시합니다. Serena의 find_symbol/read_memory와 연계해서 현재 작업에 맞는 문서만 동적으로 로드합니다.

# 살핌 CLAUDE.md (요약)
- 보고서 작성 → docs/reports/ + Serena 메모리 'writing-style'
- 생기부 변환 → docs/생기부-가이드/ + Serena 메모리 'pii-rules'
- DB 분석 → docs/database/ + Serena 메모리 'rls-policies'
- 디자인 작업 → docs/design/ + .claude/rules/cozy-garden.md

50KB를 한 번에 보여 주는 것이 컨텍스트를 제공하는 것이라면, 이 라우팅 표는 컨텍스트를 절약하는 것입니다. 핵심 아이디어는 단순합니다. 글 한 편을 통째로 두는 대신 "이 상황에서는 이걸 읽어"라는 안내문을 입구에 붙여 두면 AI가 더 정확히 일합니다. 필요한 것만 그때그때 펼쳐 보이는 방식입니다.

환경 자동 감지 + processed_files.txt 멱등성 — 담임 업무 자동화 (2026-03)

저는 노트북을 두 대 씁니다. 개인 노트북은 WSL/Ubuntu 환경, 업무용 노트북은 Windows 환경입니다. 두 노트북이 OneDrive로 같은 폴더를 공유합니다. 학교에서 안내문이 오면 어느 노트북에서든 OneDrive의 GOE/ 폴더에 던져 두고, "ㅎㅇ"이라고 입력하면 Claude Code가 그 폴더를 스캔해 분류·게시·정리합니다.

문제는 두 환경의 절대 경로가 다르다는 점입니다. WSL에서는 /mnt/c/Users/창욱/OneDrive/2026년 업무용 자료/work이고, Windows에서는 C:\Users\user\OneDrive\2026년 업무용 자료\work입니다. 절대 경로를 어딘가에 박아 두면 다른 노트북에서 깨집니다.

해결은 두 단계로 깔아 두었습니다. 첫째, 세션 시작 시 OS를 자동 판별하고(uname 가능 여부) 사용자명도 동적으로 추출합니다. 둘째, 인박스 폴더 GOE/를 항상 프로젝트 루트의 형제 폴더(../GOE/)로 고정합니다. 처리 완료 파일을 기록하는 로그/processed_files.txt에는 GOE 기준 상대 경로만 적습니다.

안내사항/2026-04/0418-체육대회.png
첨부파일/0420-수학여행준비물.hwp
첨부파일/0421-수학여행일정.pdf

이 한 가지 결정 덕분에 어느 노트북에서 처리해도 같은 파일을 두 번 처리하는 일이 없습니다. 환경 다양성이라는 문제를 폴더 구조와 로그 포맷으로 흡수한 셈입니다. 흔히 환경 분기는 코드 안에 if process.platform === "win32" 식으로 박는데, 그렇게 하면 각 스크립트마다 분기가 늘어납니다. 인프라 한 곳에 멱등성을 두면 그 위의 모든 자동화가 환경을 의식하지 않아도 됩니다.

최소 CLAUDE.md + 최대 .claude/rules 분산 — 북곽원 (2026-04)

북곽원은 정반대 방향으로 갑니다. 프로젝트 루트의 CLAUDE.md는 24줄짜리, 사실상 프로젝트 개요와 금지사항만 적힌 짧은 문서입니다. 대신 모든 규칙은 .claude/rules/ 폴더로 분산해 두었습니다.

.claude/
├─ rules/
│   ├─ design-protection.md   # design/ 폴더 수정 금지
│   ├─ jspdf-template.md      # 학교 시험지 양식 규칙
│   └─ stars-policy.md        # 게이미피케이션 보상 규칙
├─ hooks/
│   └─ pre-edit-design.sh     # 자동 차단
└─ settings.json

이렇게 한 의도는 분명합니다. CLAUDE.md는 "무엇을 만드는지"만, .claude/rules는 "어떻게/왜"만. 비개발자가 짧은 입구 문서만 보고 프로젝트 윤곽을 파악할 수 있도록 하면서, 세부 규칙은 글로브 패턴으로 자동 매칭되어 필요한 순간에만 로드되도록 했습니다. 한 문서 안에 모든 걸 욱여넣는 대신, 폴더링 자체가 라우팅 역할을 합니다. 살핌의 사례에서 한 번 길어진 문서를 라우팅 표로 풀었다면, 북곽원에서는 처음부터 입구를 짧게 만든 셈입니다.

Phaser 4 Skill Snapshot — 라이브러리 버전 고정 — 앵그리매스 (2026-04)

앵그리매스는 Phaser 4 + Matter.js 위에서 돌아갑니다. 그런데 Phaser 4는 정식 출시 직후라 온라인 문서가 자주 갱신되고, 어떤 페이지는 사라지기도 합니다. AI에게 새 기능 추가를 시킬 때마다 라이브러리 사용법을 검색해 오게 하면, 어제의 답과 오늘의 답이 달라집니다.

그래서 Phaser 4의 v4.0.0 태그 docs를 GitHub에서 직접 클론해 .claude/skills/phaser-v4/에 스냅샷으로 박아 두었습니다.

git clone --depth 1 --branch v4.0.0 https://github.com/phaserjs/phaser /tmp/phaser4
cp -r /tmp/phaser4/docs/* .claude/skills/phaser-v4/
echo "snapshot_date: 2026-03-15" > .claude/skills/phaser-v4/META.md
echo "source: github.com/phaserjs/phaser/tree/v4.0.0" >> .claude/skills/phaser-v4/META.md

이후 Claude는 항상 이 스냅샷을 기준으로 답합니다. 라이브러리 버전 드리프트는 게임처럼 작은 변경이 큰 기능 차이를 만드는 영역에서 특히 무섭습니다. 한 번 박아 둔 스냅샷이 6개월짜리 안정성을 제공합니다. 모델이 더 좋아져도 이 스냅샷은 사라지지 않습니다. 좋은 하네스는 모델 교체에 영향을 받지 않습니다.

2.2 맥락(Context) — AI가 무엇을 알고 일하는가

메뉴 ↔ 리뷰 문서 1:1 매핑 — 주도 (2025-11)

주도는 30개가 넘는 모듈로 이루어져 있습니다. 출결, 외박, 상벌점, 방과후, 건강 기록, 일일보고서, 식단, 점호, 학부모 알림, 사감 인수인계 등등. 각 모듈은 사감/사감장/교사/학생/학부모 6개 역할의 권한이 다릅니다.

이런 시스템에서 가장 무서운 건 코드와 문서의 어긋남입니다. 어제 리뷰 문서에 적힌 권한 정책과 오늘 코드의 RLS 정책이 다를 때, 누구도 그 차이를 알아채지 못합니다. 그래서 CLAUDE.md에 메뉴와 리뷰 문서의 1:1 매핑 표를 두었습니다.

| 메뉴 | 경로 | 리뷰 문서 |
|------|------|---------|
| 출석체크 (/attendance) | src/app/(main)/attendance | docs/review/2025-12-08-attendance-module-review.md |
| 외박/퇴사 (/overnight)  | src/app/(main)/overnight  | docs/review/2025-12-08-overnight-module-architecture.md |
| 방과후 (/afterschool)   | src/app/(main)/afterschool | docs/review/2026-01-15-afterschool-flow.md |
... (30개 행)

규칙은 단순합니다. 모듈 코드를 수정하기 전에 그 모듈의 리뷰 문서를 먼저 읽는다. 수정 후에는 리뷰 문서에 1~2줄 메모를 추가한다. AI가 직접 이 규칙을 따릅니다. 코드와 문서의 어긋남이 원천에서 차단됩니다. 가장 일찍 시작한 프로젝트라 시행착오를 가장 많이 겪었고, 그 결과 "코드를 만지기 전에 문서를 만진다"는 운영 원칙이 굳었습니다.

Q.E.D./P.F./Q.E.P. — 세션 핸드오프 자동화 — 살핌 (2026-01)

긴 작업을 한 세션 안에서 끝내는 건 점점 어려워집니다. 컨텍스트 윈도우는 유한하고, 80%를 넘기면 캐시 미스가 누적되어 응답이 느려지고 비싸집니다. 그래서 살핌에서는 세 글자 트리거를 깔아 두었습니다.

이름이 조금 독특해 보일 수 있습니다. 수학에는 오래된 관습이 있습니다. 증명을 시작할 때 P.F.(proof)로 운을 떼고, 증명이 끝나면 Q.E.D.(quod erat demonstrandum, "이상으로 증명을 마친다")로 닫습니다. 수학자들이 오랫동안 자기 글의 시작과 끝을 표시해 온 방식인데, 수학 교사인 저에게 그 흐름이 가장 자연스럽게 와 닿아서 명령어 이름으로 그대로 가져왔습니다.

  • P.F.: 다음 세션을 시작할 때 입력. 가장 최근의 session-context-* 메모리를 자동 로드하고 요약을 띄웁니다(증명을 시작한다는 신호).
  • Q.E.D.: 세션을 마칠 때 입력. 미완료 작업, 설계 결정, 진행 상태를 Serena 메모리에 자동 저장합니다(증명을 마친다는 신호). 메모리 이름은 session-context-YYYY-MM-DD-{topic} 규칙을 따릅니다.
  • Q.E.P.: Phase 사이의 핸드오프. 현재 Phase 결과를 메모리에 저장하고, 다음 Phase에서 이어서 시작할 수 있는 복사 가능한 프롬프트를 자동 생성합니다.
# Q.E.D. 출력 예시
✅ Saved: session-context-2026-04-28-rls-policy-rewrite
   - 미완료: school_events RLS 정책 4개 검증
   - 결정: tenant_id를 모든 RLS 정책의 첫 조건으로 두기로 함
   - 다음 세션 P.F.로 이어받기

긴 작업을 인위적으로 자르고, 자른 자리마다 메모리에 표식을 남겨 둡니다. 이 운영 메커니즘이 정착한 뒤로는 한 작업이 3~4 세션에 걸쳐도 맥락이 빠지는 일이 거의 없습니다. 컨텍스트는 채우는 것만큼이나 비우는 일이 중요합니다.

이 사례에서 한 가지 강조하고 싶은 게 있습니다. 하네스 엔지니어링은 100명이 있으면 100가지 모양이 나오는 영역이라고 생각합니다. 저는 수학 교사라 P.F./Q.E.D.가 가장 자연스러웠지만, 다른 분에게는 영어 명령어가, 또 다른 분에게는 자기 도메인의 약어가, 누군가에게는 한국어 트리거(이 글의 다른 사례인 ㅎㅇ/ㅋㅋ처럼)가 가장 손에 익을 겁니다. AI가 각 사람의 특성·일하는 방식·호흡에 맞춰 움직이도록 작업 환경을 깔아 주는 일이 하네스 엔지니어링이라면, 모두가 같은 모양을 따라가는 것보다 본인이 가장 자연스럽게 부를 수 있는 이름과 흐름으로 깔아 두는 게 맞다고 봅니다. 정답이 하나가 아닌 영역입니다.

Tauri 빌드 분기를 CLAUDE.md에 박아 두기 — NEIS 추정분할점수 계산기 (2026-04)

NEIS 추정분할점수 계산기는 Tauri 데스크톱 앱과 GitHub Pages 웹을 동시에 배포합니다. 코드는 한 벌인데 빌드 타깃이 둘이라, AI에게 새 기능 구현을 시킬 때 데스크톱 분기와 웹 분기를 잊지 않게 해야 합니다.

이걸 CLAUDE.md에 한 단락으로 박아 두는 것을 추구해 왔습니다.

# Tauri/웹 이중 배포 규칙
- 파일 시스템 접근: useIsTauri() ? @tauri-apps/plugin-fs : 웹스토리지/다운로드 fallback
- 다이얼로그: useIsTauri() ? @tauri-apps/plugin-dialog : window.confirm/prompt
- 자동 업데이트: 데스크톱만, 웹은 새로고침 안내
- 새 기능 추가 시 두 분기 모두 작성하지 않으면 PR 차단

이 규칙이 정착되면 AI가 "이 기능은 Tauri만 지원되네요"라고 절반만 만들고 끝내는 일이 사라집니다. 빌드 타깃 두 개를 문서 수준에서 강제하는 것이 가장 단순한 검증입니다. 솔직히 말씀드리면 이 프로젝트는 아직 CLAUDE.md가 정착 중인 단계입니다. GitHub Releases 자동 퍼블리시(아래에서 다시 다룹니다)는 잘 굳었지만, 빌드 분기 규칙은 추구해 가는 중입니다.

6계층 변환 스택 + 실패 10패턴 — 앵그리매스 (2026-04)

수학식을 게임 메커닉으로 바꾸는 일은 생각보다 까다롭습니다. 사용자가 입력한 함수 f_user는 디버프 변환 T_debuff를 거쳐, 월드 좌표 originWorld로 옮기고, 로컬 좌표 hLocal/kLocal을 거쳐, 최종 발사 방향 dir로 평가됩니다. 이게 6단계입니다.

그런데 이 변환을 일관되게 짜는 게 사람에게도 어렵습니다. AI에게 시키면 더 자주 실수합니다. 그래서 src/math/CLAUDE.md에 6계층의 역할과 AI가 흔히 빠지는 함정 10가지를 모두 적어 두었습니다.

# 변환 스택 6계층
f_user → T_debuff → originWorld → hLocal → kLocal → dir

# 흔한 실패 패턴
1. f(-x) 전환을 originWorld 단계가 아니라 f_user 단계에서 적용 → 디버프와 충돌
2. setFlipX를 물리 엔진 객체에 직접 적용 → 충돌 처리가 깨짐
3. ValidateAST에서 노드 상한 50을 검증하지 않고 통과 → 무한 평가 루프
... (총 10개)

각 함정마다 어떤 디버그 API(debugState(), debugTransformedJson())로 검증할지를 함께 적어 두었습니다. 이 문서가 있은 뒤로 새 디버프를 추가할 때 어디만 만지면 되는지 명확해졌습니다. 실패 사례를 미리 박제해 두면 AI는 같은 실수를 반복하지 않습니다. 주도의 메뉴↔리뷰 매핑이 "어디를 봐야 하는지"를 알려준다면, 이 문서는 "어디서 자주 넘어지는지"를 미리 알려 줍니다.

2.3 계획(Planning) — 일을 시키기 전에 무엇을 할지 정한다

자연어 트리거 라우팅 — 담임 업무 자동화 (2026-03)

업무 자동화 시스템에는 슬래시 커맨드가 없습니다. 대신 한국어 트리거가 있습니다. 자주 쓰는 9개입니다.

  • ㅎㅇ: 인박스 확인. GOE 폴더 스캔, 안내사항/첨부파일 매칭, Slack 정리 제안.
  • ㅋㅋ: 학생 참여 콘텐츠 제안. 오늘의 한마디, 주간 퀴즈, 생일 축하.
  • RAG: 자료 검색. GOE/처리완료/문서보관/자료 폴더 모두 검색해서 답변.
  • 일합시다: 업무 폴더 협업 모드. HWP/PDF 문서를 함께 검토·편집.
  • 백업해줘: Slack 90일 만료 메시지 로컬 백업/복원.
  • 내일 아침조회 정리해: 다음 등교일 학급 + 1학년부 조회 브리핑.

각 트리거는 CLAUDE.md의 매핑 표에 적혀 있고, 표 자체가 사용설명서를 겸합니다.

| 트리거 | Skill 호출 | 비고 |
|--------|----------|------|
| "ㅎㅇ", "인박스 확인" | classroom-manager | 인박스 처리 |
| "ㅋㅋ", "콘텐츠 올려" | classroom-manager | 학생 참여 콘텐츠 |
| "RAG", "자료 찾아줘" | classroom-manager | 자료 검색 모드 |
... (9행)

이 사례에서 의도는 분명합니다. 명령어를 외울 필요가 없게 하는 것. 카카오톡에 답장 보내듯이 "ㅎㅇ"이라고 치는 자연스러운 동작이 곧 9단계 자동 워크플로우의 진입점이 됩니다. 도메인 언어로 복잡한 멀티스텝을 캡슐화한 셈입니다.

Plan.md 스코프 락 — 개인용 살핌 (2026-04)

개인용 살핌은 Tauri로 만든 데스크톱 앱입니다. 학생 데이터가 클라우드에 올라가지 않고 로컬 SQLite에만 저장됩니다. 작은 앱이라 자칫하면 "이 기능도 있으면 좋겠다"가 끝없이 늘어납니다.

그래서 docs/plan.md에 v1 범위를 명시적으로 잠가 두었습니다.

# v1 화면 (확정)
- Bar 모드: 한 줄 입력
- Side 모드: 누가기록 확인
- Full 모드: 학기 정리

# v1 데이터 모델 (확정)
- students, observations, school_events, exports

# v2 후보 (현재 구현 금지)
- 학교용 살핌과의 클라우드 동기화
- 모바일 컴패니언 앱
- 외부 LLM 미세조정

새 기능을 추가하기 전에 plan.md의 v1 섹션과 대조하는 단계를 의무로 두었습니다. plan.md에 없으면 v2 후보로 보내고, v2 후보는 현재 세션에서 구현하지 않습니다. AI가 "더 좋게 해드릴게요"라며 친절하게 범위를 넓히는 일이 잦은데, 이 잠금이 그 친절을 차단합니다.

Phase 게이트 자동화 — 앵그리매스 (2026-04)

앵그리매스는 Phase 0부터 Phase 14까지 15단계의 큰 마일스톤이 있습니다. 각 Phase마다 docs/phases/phase-NN-*.md 문서가 있고, 인수 조건과 테스트 케이스가 박혀 있습니다. Phase 종료 시점에 다음 자동화가 돌아갑니다.

# Phase 종료 체크리스트 (자동)
1. lint 통과 (pnpm lint)
2. typecheck 통과 (pnpm tsc --noEmit)
3. 단위/통합 테스트 통과 (pnpm test)
4. phase-NN.md의 모든 인수 조건 체크
5. git commit "feat(phase-NN): ..."
6. /clear → 다음 세션 컨텍스트 초기화

다섯 항목 중 하나라도 실패하면 Phase가 안 닫힙니다. 모두 통과하면 commit과 동시에 컨텍스트를 비웁니다. 다음 Phase는 깨끗한 상태에서 시작합니다. 작업을 인위적으로 자르고 자른 자리마다 검증을 두는 운영이 정착되면, 한 게임이 15단계로 갈수록 산출물 품질이 떨어지지 않습니다. 개인용 살핌의 plan.md가 "어디까지만 만들지"를 잠갔다면, 앵그리매스의 Phase 게이트는 "한 단계가 닫히려면 무엇을 통과해야 하는지"를 잠갔습니다.

2.4 실행(Orchestration) — 어떻게 시키는가

SessionStart + PostToolUse 훅 자동 워크플로우 — 담임 업무 자동화 (2026-03)

업무 자동화는 훅에 많이 의존합니다. 사용자가 명시적으로 시키지 않아도, 어떤 사건이 발생하면 자동으로 부수 작업이 따라붙도록 깔아 두었습니다.

  • SessionStart: 매일 첫 세션이 시작될 때 한 번 실행. 어제까지의 일정 라인을 자동으로 제거하고, Slack 일정 채널의 pin 메시지를 새 내용으로 교체합니다. 당일 일정이 있으면 학생들에게 DM을 보냅니다.
  • PostToolUse Edit/Write: 일정 파일을 수정한 직후 발동. 수정 전후를 비교해 오늘 일정에 변경이 있었는지를 판별합니다. 변경이 있으면 학생 전체에게 "🆕 업데이트" DM을 자동 발송합니다.
  • PreToolUse Bash: Slack CLI를 학생 워크스페이스에 보낼 때 발동. 메시지에 학생 이름·연락처·성적 같은 개인정보가 섞여 있으면 차단합니다.
# settings.local.json (요약)
{
  "hooks": {
    "SessionStart": [".claude/hooks/daily-briefing.sh"],
    "PostToolUse": {
      "Edit|Write": [".claude/hooks/schedule-sync.sh"]
    },
    "PreToolUse": {
      "Bash": [".claude/hooks/slack-pii-guard.sh"]
    }
  }
}

한 번의 행위(파일 수정)가 여러 채널로 자동 확산됩니다. 담임이 일정을 고치면, 그 결과가 자동으로 Slack pin 교체 + DM 발송으로 이어집니다. "이제 pin 교체하러 가야 하지" 같은 부수 작업의 인지 부하를 0으로 줄였습니다.

GitHub Releases 자동 퍼블리시 — NEIS 추정분할점수 계산기 (2026-04)

NEIS 추정분할점수 계산기는 Tauri 데스크톱과 웹 두 갈래로 배포됩니다. 두 빌드를 사람이 매번 돌리면 실수가 잦습니다. 그래서 .github/workflows/release.ymldeploy-web.yml을 두 갈래로 나눠 두었습니다.

# release.yml (Tauri 데스크톱)
name: Release
on:
  push:
    tags: ['v*']
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [windows-latest, macos-latest, ubuntu-latest]
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install
      - run: pnpm tauri build
      - uses: tauri-apps/tauri-action@v0
        with:
          releaseName: 'NEIS CutScore __VERSION__'

웹은 별도 워크플로우에서 GitHub Pages로 배포합니다. 태그 푸시 한 번이면 윈도/맥/리눅스 설치 파일이 동시에 만들어지고, 웹도 갱신됩니다. AI가 릴리즈 단계에서 빠뜨릴 일이 사라집니다. 담임 자동화가 사용자 입력 한 번에 부수 작업을 자동 확산했다면, 여기서는 git 태그 한 번이 빌드와 배포 전체를 자동 확산합니다.

전역 /loop 스킬 — 자율 구현-테스트-수정 루프 — 앵그리매스에서 활용 (2026-04)

이건 글로벌 스킬입니다. 모든 프로젝트에서 쓸 수 있도록 만들어 두었지만, 가장 효과를 본 건 앵그리매스 빌드 과정이었습니다. 게임은 버그가 많고, 그 버그가 수식·물리 엔진·렌더링 어디에서 시작됐는지 디버깅이 어렵습니다.

루프 스킬의 핵심은 다음과 같습니다.

# /loop 워크플로우
0. .dev-loop-active 플래그 파일 생성 → 훅 활성화
1. 요구사항을 .dev-loop-checklist.md에 캡처
2. 구현
3. 매 라운드 시작 전 체크리스트 다시 읽기
4. 개발 서버에서 항목별로 테스트 (스크린샷 포함)
5. 실패 항목 → Codex --write --effort high에 위임
6. Codex 수정 후 전체 재테스트
7. 모든 항목 통과 → .dev-loop-active 삭제 → 훅 비활성화
8. "루프를 완료했어요!" 보고

여기에 가드 훅 두 개가 붙습니다.

  • dev-loop-edit-guard.sh (PreToolUse Edit/Write): .dev-loop-active가 있는데 체크리스트가 없으면 코드 수정 차단.
  • dev-loop-stop-guard.sh (Stop): 체크리스트에 미완료 항목이 있으면 세션 멈춤 차단.

게임을 만들 때 가장 도움이 됐던 부분은 4번입니다. AI가 직접 개발 서버에 접속해서 스크린샷을 찍으며 의도대로 돌아가는지 확인합니다. 함수가 포탄으로 그려지는 궤적을 사람 눈으로만 검증하면 시간이 오래 걸리는데, AI가 시각 검증을 직접 돌리고 통과 여부를 체크하는 흐름이 정착되니 한 번 시작한 루프가 사람 개입 없이 닫히는 일이 잦아졌습니다. 4시간이 걸릴 수도 있고, 30분 안에 끝날 수도 있습니다. 어쨌든 사람 손은 시작과 최종 확인에만 들어갑니다.

2.5 검증(Verification) — 결과물을 어떻게 믿을 것인가

가장 비중을 두고 있는 축입니다. AI 산출물을 어떻게 믿을지가 결국 모든 자동화의 천장을 결정합니다.

iOS ITP + RPC 패턴 강제 — 문서/코드 다중 검증 — 주도 (2025-11)

주도는 모바일에서 많이 쓰입니다. 학생들은 iOS Safari, 학부모는 다양한 브라우저, 사감은 데스크톱입니다. iOS Safari의 추적 방지 기능(Intelligent Tracking Prevention, ITP)은 서드파티 쿠키와 일부 fetch 패턴을 차단해서 인증 무한 루프를 일으킵니다.

해결은 세 가지를 동시에 강제하는 것입니다.

# CLAUDE.md (요약)
- REST API 대신 SECURITY DEFINER RPC 사용 (Supabase)
- Mutation 성공 후 invalidateQueries 대신 setQueryData
- RPC 함수의 역할 체크 = RLS 정책의 역할 목록 = 일치해야 함

이 규칙은 세 계층에서 검증됩니다. 첫째 운영 문서(CLAUDE.md). 둘째 코드 패턴(useAddBlockRow, useDeleteSeatBlock 등 기존 훅에 박혀 있음). 셋째 pnpm check:types(생성된 Supabase 타입과 RPC 호출의 타입 불일치를 컴파일 시점에 잡음). 한 계층이 뚫려도 다음 계층에서 잡힙니다.

Supabase 1000행 한계 자동 감지 — 주도 (2025-11)

Supabase REST API는 기본적으로 한 쿼리에 1000행만 반환합니다. 출결 테이블은 학생 265명 × 4교시 = 1060행이라 조용히 누락되는 상황이 가능합니다.

이 위험을 코드 리뷰 단계가 아니라 작성 단계에서 차단하기 위해 두 가지를 깔아 두었습니다. 첫째, docs/database/@20251209-supabase-pagination-guide.md에 페이지네이션 패턴을 도큐멘트로 남겼습니다. 둘째, 모든 .select() 호출 위에 예상 행 수 주석을 붙이는 규칙을 두었습니다.

// 예상 행 수: 265명 × 4교시 = 1060행, 페이지네이션 필수
const allAttendance = await fetchAllPaginated(
  () => supabase.from('attendance').select('*').order('id'),
  { pageSize: 500 }
);

코드를 짤 때 예상 행 수를 적도록 하면 1000을 넘는 순간 본인이 페이지네이션을 의식하게 됩니다. 검증을 가장 작은 비용으로 가장 이른 시점에 박은 사례입니다.

Hookify 디자인 토큰 강제화 — 살핌 (2026-01)

살핌의 디자인 시스템은 Cozy Garden 컬러 팔레트를 따릅니다. 라이트모드 전용, 시맨틱 토큰만 허용, 하드코딩된 HEX 색상 금지, 둥근 모서리 rounded-xl 이상 금지 같은 규칙이 있습니다.

규칙은 문서에도 적혀 있지만, 사람이 (특히 AI가) 이걸 매번 의식하면서 코드를 짜기는 어렵습니다. 그래서 .claude/hookify.cozy-*.local.md 파일 다섯 개로 자동 차단을 깔아 두었습니다. 파일을 저장할 때마다 정규식으로 위반을 검사합니다.

# hookify.cozy-hardcoded-colors.local.md
trigger: file save (*.tsx, *.ts, *.css)
pattern: /#[0-9a-fA-F]{3,8}\b/
action: warn
message: |
  하드코딩된 HEX 색상 발견. CSS 변수(var(--cozy-*))로 변환하세요.
  허용된 7개 색상 외에는 모두 위반입니다.

비개발자도 규칙을 외우지 않아도 위반하는 즉시 경고를 받습니다. 디자인 일관성을 사람의 기억력이 아니라 인프라가 지킵니다.

역할 검증 스킬 + RLS 정책 자동 대조 — 살핌 (2026-01)

살핌은 담임/담당/일반/학생 4역할이 있고, 각 기능마다 접근 권한이 다릅니다. 권한은 두 군데에 적혀 있습니다. 하나는 .claude/skills/role-based-workflow/permission-matrix.md(사람이 읽는 명세), 다른 하나는 Supabase의 RLS 정책(코드가 따르는 실제 규칙). 이 둘이 일치하지 않으면 사고가 납니다.

/peem-role 스킬이 둘을 자동 대조합니다.

# /peem-role 출력 예시
✅ school_events
   permission-matrix: 담임=R/W, 담당=R/W, 일반=R, 학생=X
   RLS 정책: 일치
✅ observations
   permission-matrix: 담임=R/W, 담당=R/W(자기 학생만), 일반=X, 학생=X
   RLS 정책: 일치
❌ student_journeys
   permission-matrix: 학생=R(자기 것만)
   RLS 정책: 학생=R(전체) ← 누락된 자기 필터

명세와 코드의 어긋남을 사람이 발견하기 전에 스킬이 잡아냅니다. RLS는 한 번 잘못 깔리면 보안 사고로 이어지기 때문에, 가장 깊은 층에 검증을 박는 것을 추구해 왔습니다.

AskUserQuestion 설문 게이트 — 담임 업무 자동화 (2026-03)

업무 자동화의 마지막 단계는 거의 항상 Slack 게시입니다. 게시는 되돌릴 수 없습니다. 50명 학생과 교사 다수에게 즉시 전파되기 때문입니다. AI가 만든 메시지를 그대로 보내면 사고가 납니다.

그래서 게시 직전에 항상 AskUserQuestion 설문이 끼어듭니다.

# 설문 예시
Q. 다음 메시지를 #일정 채널에 보낼까요?
   "내일 4월 29일은 5교시 후 2시 50분 하교, 동아리 활동 없습니다."

A. 이대로 보내기
B. 메시지 수정 후 보내기
C. 채널을 #공지로 변경하기
D. 보류

타이핑 없이 클릭만으로 검수가 끝납니다. 10초짜리 게이트가 50명에게 잘못된 메시지가 전파되는 사고를 막습니다. 사람의 마지막 확인을 가장 가벼운 비용으로 받는 인프라입니다.

보안성검토-하/중/상 — 멀티 AI 협업 검증 — 북깍스토어에서 활용 (2026-03)

이건 글로벌 스킬입니다. ~/.claude/commands/보안성검토-하.md, 보안성검토-중.md, 보안성검토-상.md 세 등급으로 만들어 두었습니다. 등급에 따라 검증 강도와 비용을 의식적으로 조절합니다.

  • 하 (빠른 스캔): Claude Sonnet만 사용. OWASP Top 10 그렙 패턴 + Phase 0 라우팅 계획 선언 + Phase 4 보고서. 5~10분.
  • 중 (표준 감사): 4 AI 협업. Phase 1에서 Gemini(공격면 스캔) + Codex(보안 도구 실행) + Claude Sonnet(패턴 매칭) 세 작업을 단일 메시지에서 병렬 호출. Phase 2에서 Claude Opus가 교차 검증. 20~30분.
  • 상 (심층 감사): 중에 Phase 3 토론 단계 추가. 4 AI가 각자 독립 의견을 내고, Opus가 토론을 진행하며 합의를 도출. 1~2시간.
# 보안성검토-중.md 핵심 규칙
⛔ Claude(opus/sonnet)가 직접 전체 코드베이스를 읽으며 보안 분석하는 것
⛔ Phase 1에서 Gemini/Codex 호출 없이 Phase 2로 넘어가는 것
✅ 대규모 스캔 → bash ~/.claude/scripts/gemini-run.sh "프롬프트"
✅ 보안 도구 실행 → bash ~/.claude/scripts/codex-run.sh "프롬프트"
✅ 패턴 매칭 → Agent(model:sonnet)
✅ 교차 검증 → Agent(model:opus)
✅ Phase 1의 3개 작업은 반드시 단일 메시지에서 병렬 호출

이 글로벌 스킬을 북깍스토어 빌드 과정에 적용했습니다. 학교 서비스라 보안 사고가 나면 안 되고, 학생들이 직접 앱을 올리는 만큼 공격 표면이 넓었습니다. 중 등급으로 한 번에 17건의 취약점을 찾아 일괄 수정했습니다. SSRF 방어, Supabase Storage 업로드 제한, IP당 레이트 리밋, 로그인 잠금 등이 그 결과입니다. 4 AI가 같은 코드베이스를 다른 관점으로 봅니다. 한 모델의 맹점을 다른 모델이 잡습니다. 같은 컨텍스트에서 만들고 평가하면 자기 작업을 과대평가하기 쉬운데, 만든 AI와 평가하는 AI를 다른 모델·다른 컨텍스트로 분리해 둔 셈입니다.

이 사례 하나로 기존에 코드 리뷰 한 번에 1~2주 걸리던 보안 점검이 30분 안에 1차로 끝나고, 사람은 우선순위 판단에만 시간을 씁니다. 보안은 상황별로 검증 강도를 조절한다는 철학이 글로벌 스킬에 박혀 있고, 그게 모든 학교 서비스 빌드에 동일한 안전망을 깔아 줍니다.

설계 보호 PreToolUse 훅 — 디자인 폴더 Edit 차단 — 북곽원 (2026-04)

이 사례는 한 가지 깨달음에서 시작했습니다.

처음에는 Claude에게 UI 컴포넌트를 직접 만들도록 시켰습니다. 매번 결과물의 톤이 미묘하게 달랐고, 며칠 지나면 디자인 일관성이 무너졌습니다. 그러다 알게 됐습니다. 프론트엔드 디자인은 레퍼런스가 결정한다. Variant.com에서 만든 디자인 원본을 그대로 익스포트해 와서 그게 진실의 원본이 되어야 한다, 그리고 AI가 거기에 손을 대면 안 된다.

그 깨달음을 인프라로 굳힌 게 이 훅입니다.

# .claude/hooks/pre-edit-design.sh
#!/bin/bash
# 설계 보호: design/ 폴더는 Variant 원본이라 Edit 자체 차단
if echo "$FILEPATH" | grep -q 'src/components/design/'; then
  if [ "$TOOL" = "Edit" ] || [ "$TOOL" = "Write" ]; then
    echo "BLOCKED: design/ 폴더는 Variant 원본입니다."
    echo "스타일 변경이 필요하면 사용자에게 보고만 하세요."
    exit 1
  fi
fi
exit 0

.claude/rules/design-protection.md에는 허용과 금지가 명시되어 있습니다.

# design-protection.md
허용:
- 이벤트 핸들러 추가/수정 (onClick, onChange 등)
- aria-* 속성 추가
- 텍스트 카피 교체 (copy.md 경유)

금지:
- className 수정
- Tailwind 클래스 추가/제거
- 인라인 style 추가
- 레이아웃 구조 변경 (div 추가/이동)

이 한 가지 규칙이 정착된 뒤로 디자인이 흐트러지지 않습니다. AI가 "더 좋게 만들어 드리겠습니다"라며 손을 대는 길 자체를 차단했기 때문입니다. 깨달음이 인프라로 굳는 것, 이게 하네스 엔지니어링의 한 흐름이라고 생각합니다.

2.6 개선(Compound) — 어떻게 계속 나아질 것인가

tasks/ 디렉토리 — 버그 분석 문서화 — 주도 (2025-11)

주도에는 tasks/ 디렉토리가 있습니다. 매 버그마다 한 파일이 추가됩니다. 파일 이름은 YYYY-MM-DD-제목.md이고, 내용은 문제 → 근인 → 솔루션 → 검증 4단 구조입니다.

# tasks/2026-02-24-delete-student-hard-409-conflict-fix.md

## 문제
delete_student_hard RPC가 409 Conflict 반환.

## 근인
profiles.id를 참조하는 ~70개 테이블에서 NO ACTION/RESTRICT FK 정책.
profile 삭제가 cascade되지 않아 충돌.

## 솔루션
cleanup_profile_references() 헬퍼 함수 신설.
delete_student_hard, delete_student_soft 두 RPC가 이 헬퍼를 호출하도록 수정.
실행 흐름: 1) FK 참조 테이블에서 NULL 처리 또는 cascade → 2) profile 삭제.

## 검증
- cleanup_profile_references() 함수 존재 확인
- delete_student_hard 실행 후 RLS 정책 무결성 확인
- 70개 테이블의 FK 정책이 의도대로 작동하는지 확인

이 형식이 굳은 뒤로 PR 직전에 본인이 다시 한 번 왜 이렇게 풀었는지를 문서로 검토합니다. 비슷한 버그가 다시 나오면 이 폴더를 검색합니다. "지라 티켓을 닫는 행위"가 "문서를 남기는 행위"로 바뀌니, 6개월 뒤에 와서도 누가 왜 이걸 고쳤는지 한 페이지로 압니다.

main push 후 changelog 강제 리마인더 — 북곽원 (2026-04)

북곽원에는 update_logs 테이블이 있습니다. 사용자(학생들)에게 보여 주는 변경 이력입니다. 그런데 main에 push한 직후 changelog 갱신을 깜빡하는 일이 잦았습니다.

PostToolUse 훅을 깔아 두었습니다.

# .claude/hooks/post-bash-changelog-reminder.sh
# Bash 도구 실행 후 git push가 감지되면 다음 턴에 강제 리마인더 삽입
if echo "$BASH_COMMAND" | grep -q 'git push.*main'; then
  cat << EOF
=== UPDATE_LOG_REMINDER (MANDATORY) ===
main 브랜치에 push되었습니다.
사용자 경험에 영향을 주는 변경사항이 있는지 판단하고,
있다면 반드시 update_logs 테이블에 INSERT 하세요.
EOF
fi

다음 턴 초입에 강제 메시지가 삽입됩니다. AI는 그 메시지를 보고 changelog를 갱신할지 판단합니다. 깜빡할 자유 자체가 사라집니다. 주도의 tasks/ 디렉토리가 사람이 직접 적는 회고였다면, 여기서는 push라는 행위 자체가 회고를 강제하는 트리거가 됩니다.

Dev Loop Checklist — 다중 작업 동시 추적 — 개인용 살핌 (2026-04)

개인용 살핌의 루트에는 .dev-loop-checklist.md가 있습니다. 위에서 다룬 글로벌 /loop 스킬과 짝을 이룹니다.

# .dev-loop-checklist.md (예시)

## 우선순위
- [ ] T-001: Bar 모드에서 한 줄 입력 → SQLite INSERT
- [ ] T-002: 입력값에 학생명 자동 추출 (NLP)
- [ ] T-003: 디자인 정책 위반 검사 (hookify)
- [ ] T-004: 빌드 통과 (pnpm tauri build)

## 완료 기준
- pnpm build 통과
- pnpm tauri build 통과
- 각 항목당 변경 파일 로그 남김

여러 작업을 동시에 진행할 때, 각 항목마다 검증 로그가 누적됩니다. 누락 없이 한 라운드를 닫는 운영이 정착됩니다.

Decision Log + PreToolUse 훅 — 앵그리매스 (2026-04)

앵그리매스에는 docs/decisions.md가 있습니다. 모든 코드 변경에는 의사결정 사유를 기록해야 합니다. 현재까지 D-001부터 D-095까지 95건이 누적되어 있습니다.

# decisions.md 항목 예시
## D-056. 관통미사일에 다항함수만 허용 (2026-02-14)

Context: 관통미사일을 자유 입력으로 두자, 학생이 10*sin(x^100) 같은 급진동
함수로 맵 전체를 뚫는 exploit이 발생.

Decision: ValidateAST에서 관통미사일 입력은 다항함수만 허용. 초월함수
(sin/cos/tan/exp/log) 금지.

Consequence: 학생 창의력을 일부 제한. 단, 한 학생이 테일러 근사로
사인함수에 가까운 다항식을 만드는 시도가 등장 → 제약이 더 깊은 수학으로
학생을 끌고 감.

기록만 하는 게 아닙니다. PreToolUse 훅이 코드 편집 시 decisions.md 갱신을 강제합니다.

# .claude/hooks/require-decision-log.py
# Edit/Write 도구 사용 시 decisions.md 변경이 함께 있는지 검사
# src/ 또는 tests/ 파일이 수정되면 docs/decisions.md도 같이 수정해야 통과

95건이 누적되니 재발 패턴이 보입니다. "비슷한 결정이 D-023, D-067에서도 있었구나, 이번엔 그때보다 한 단계 더 나아간 결정이구나." 패턴 인식이 미래의 의사결정을 더 빠르게 만듭니다. 주도에서 시작된 "기록을 통한 회고" 운영이, 앵그리매스에서는 매 코드 편집마다 자동 강제로까지 진화한 셈입니다.

3. 자기 진단 — 잘 가고 있는지 점검

여기까지 사례를 적었습니다. 그럼 우리가 정말 잘 가고 있는가, 아니면 그냥 흩뿌려져 있는가. 흔히 거론되는 자기 진단 신호 여덟 개를 우리 프로젝트에 비춰 보겠습니다.

신호우리의 증거
같은 말 두 번 안 함DoRm tasks/lessons.md 9패턴 누적 + 살핌 Q.E.D./P.F.
실수가 규칙이 됨DoRm "지침 축적 → 승격" 메커니즘
차단 장치가 작동북곽원 설계 보호 훅, 살핌 hookify 5개, 글로벌 /loop 가드 훅
불필요한 것이 줄어듦DoRm 글로벌 스킬은 5개로 압축 (자주 쓰는 것만 유지)
검수에 시간이 더 걸림 (실패 신호)보안성검토-하/중/상 분기로 검수 강도 조절하며 대응 중
시켰는데 결과가 안 나옴 (실패 신호)KB 작업 인터뷰 메모리로 보강. "코드만 보고 추론하지 말고 사용자에게 물어라"
스킬이 많은데 잘 안 쓴다 (실패 신호)글로벌은 5개로 한정. 살핌만 12개를 유지하지만 사용 빈도 모니터링 중
가이드 파일이 길어지고 관리 안 됨 (실패 신호)살핌 50KB CLAUDE.md를 라우팅 표로 분할. 북곽원은 24줄 입구로 압축

부끄러운 부분도 있습니다. 북깍스토어CLAUDE.md는 11바이트(@AGENTS.md만 참조)에 불과합니다. NEIS 추정분할점수 계산기는 CLAUDE.md가 아예 없습니다. 자기설명 LLM 평가 시스템(석사 논문 프로젝트)은 archived 상태라 빌드 하네스가 거의 없습니다.

이걸 글에서 숨기지 않는 이유는 단순합니다. 우리가 완벽한 수준에 도달했다고 말하면 거짓이기 때문입니다. "추구하며 노력 중"이라는 표현이 정확합니다. 어떤 프로젝트는 잘 굳었고, 어떤 프로젝트는 굳을 차례를 기다리고 있고, 어떤 프로젝트는 archived가 되어 더는 굳을 일이 없습니다. 좋은 하네스는 점점 단순해진다고 했습니다. 단순해지려면 우선 무엇이 있는지부터 솔직히 펼쳐 놓아야 합니다.

좋은 신호 하나만 짚자면, 복잡해지는 것이 아니라 단순해지고 있다는 점입니다. 살핌은 50KB CLAUDE.md지만 사람이 직접 읽을 부분은 입구의 라우팅 표 한 페이지뿐입니다. 글로벌 스킬은 처음에는 12개였다가 5개로 줄였습니다. 자주 쓰는 것만 남기고 나머지는 정리했습니다. 1년 전과 비교하면 지금 우리는 더 적은 도구로 더 많은 일을 합니다.

여기까지 사례를 길게 적고 나니 너무 딱딱하게 설명한 것 같아서 마음이 조금 쓰입니다. 한 가지만 덧붙이겠습니다.

가장 중요한 건 처음부터 완벽한 시스템을 설계하려 들지 않는 것입니다. 일단 에이전트를 실행해 보고, 실패하는 자리가 보이면 그때 그 자리를 막는 구조를 추가하는 흐름이 하네스 엔지니어링의 자연스러운 모양입니다. 미리 그리는 거창한 설계도가 아니라, 실패가 남긴 흔적이 곧 하네스가 됩니다. 처음부터 모든 가드와 모든 규칙을 깔아 두려 하면 한 번도 발생하지 않을 문제를 막느라 인프라가 무거워집니다. 일단 돌리고, 넘어지는 자리에만 기둥을 박는 게 더 가볍고 빠릅니다.

예컨대 한 주에 한 번씩 그 주에 에이전트가 무엇에서 넘어졌는지를 모아 보고, 다음 주에 같은 자리에서 다시 넘어지지 않도록 구조를 한두 줄씩 추가하는 시간을 가져 보는 식입니다. (이 마저도 스킬 같은 거로 만들면 어떨까요?) 그러면 시간이 흐를수록 에이전트가 같은 실수를 반복하지 않고 점점 더 신뢰할 만해집니다. 일회적인 개선이 아니라 누적되는 개선입니다.

조금 더 욕심을 내자면 "한 주에 한 번 점검"을 진짜 운영 습관으로 만드는 작은 도구를 함께 깔아 두는 것도 좋습니다. 예를 들어 OpenRouter 같은 외부 모델 게이트웨이를 통해 Claude 옆에 다른 모델들을 함께 두고, 작업 종류에 따라 라우팅하는 작은 스킬을 만들어 두는 식입니다. Kimi(전반적으로 고성능인데 토큰 단가가 낮습니다), DeepSeek(수학 추론·코딩·논리 사고에 강합니다), Gemini Flash 3(현재 가성비 대표 모델입니다) 같은 후보를 클로드에게 도구처럼 쥐여 주고, 터미널 명령어 실행이나 단순 디버깅처럼 굳이 가장 비싼 모델이 필요하지 않은 작업은 그쪽으로 라우팅합니다. 특정 행동(예: 자동 포맷팅, .env 보호)은 훅으로 강제해 두면 됩니다. 그러면 같은 회고와 같은 자동화를 더 적은 토큰으로 굴릴 수 있고, 절약한 자원은 정말 사람의 판단이 필요한 자리에 다시 쓸 수 있습니다. 위에서 정리한 여섯 단어(스킬·서브에이전트·훅·플러그인·MCP·CLI)가 사실 이 라우팅을 직접 깔 때 그대로 쓰입니다. 저희가 보안 검토에서 이미 쓰고 있는 gemini-run.sh/codex-run.sh CLI 호출이 이 흐름의 가장 얕은 형태이고, 여기에 라우팅 스킬과 가드 훅을 한 겹 더 얹으면 일상 작업 전반으로 확장됩니다.

이 흐름을 짧고 분명하게 정리한 사람이 Terraform·HashiCorp의 창업자 Mitchell Hashimoto입니다. 그가 자기 블로그(mitchellh.com/writing/my-ai-adoption-journey)에 남긴 표현을 옮기면, "에이전트가 실수하는 모습을 발견할 때마다, 그 실수를 다시 하지 않도록 시간을 들여 시스템을 손본다"는 것입니다. 그리고 그 손본 흔적 한 줄 한 줄이 쌓여 결국 거의 모든 잘못된 행동이 사라진다고 그는 덧붙였습니다. 미리 그리는 큰 설계가 아니라, 작은 실패에 매번 작은 답을 붙이는 일이 가장 강력한 자동화가 된다는 이야기입니다.

저도 같은 길 위에 있다고 생각합니다. 이 글에 담은 사례들 가운데 처음부터 의도해서 깐 것은 거의 없습니다. 대부분 한두 번의 실패가 먼저 있었고, 그 자리를 막다가 인프라로 굳었습니다. 그래서 처음 시작하시는 분들도 부디 너무 무겁게 시작하지 않으셨으면 좋겠습니다. 작게 깔고, 자주 넘어지고, 넘어진 자리에만 기둥을 박으시면 됩니다. 그게 결국 가장 빠른 길이라고, 저는 한 주씩 더 믿게 됩니다.

4. 마무리

다시 첫 줄로 돌아오겠습니다. 저는 개발자가 아닌 교사입니다.

이 사실이 이 글의 출발이고 이 글의 결론이기도 합니다. 교사가 코드를 직접 쓰지 못한다는 의미가 아닙니다. AI가 코드를 충분히 잘 써 주는 시대에, 코드 한 줄 한 줄을 사람이 다듬는 일이 더는 가장 중요한 일이 아니라는 뜻입니다. 프롬프트도 마찬가지입니다. AI가 AI한테 더 잘 시킵니다. 컨텍스트도 자동 축적이 잘 됩니다. 그럼 사람이 마지막까지 챙겨야 하는 영역이 어디인가. 환경 자체입니다. 폴더 구조, 규칙 문서, 검증 게이트, 도구 배치, 핸드오프 절차, 누적 메모리. 이런 것들을 사람이 정해 줘야 AI가 일관되게 좋은 일을 합니다.

카파시의 LLM wiki가 화제가 된 이유도 비슷합니다. 거창한 검색 인프라가 아니라, 평범한 마크다운 폴더 하나가 누적되는 단순함이 세상을 움직입니다. 우리도 그 단순함을 모방해서 한국 학교 도메인 위에 천천히 쌓아 가고 있습니다. DoRm.dev의 kb_projects/kb_nodes/kb_blog_links 테이블 셋은 카파시의 LLM wiki와 같은 결입니다. 다른 점은 한국 학교의 도메인 위에 얹혀 있다는 것뿐입니다. 시간이 더 흐르면 둘은 비슷한 모양으로 수렴할 것 같습니다.

이 글에 적은 사례들이 정답이라는 뜻은 아닙니다. 어떤 사례는 6개월 뒤에 사라질 것이고, 어떤 사례는 더 단순한 형태로 굳을 것이고, 어떤 사례는 글로벌 스킬로 승격될 것입니다. 좋은 하네스는 점점 단순해진다고 했습니다. 이 글이 1년 뒤에 다시 쓰인다면 사례 수가 절반쯤으로 줄어들 것 같습니다. 그게 저의 다음 목표입니다.

마지막으로, 혹시 이 글을 읽으신 다른 교사 분이 계시다면 한 가지 권하고 싶습니다. AI를 잘 쓴다는 건 프롬프트를 잘 다듬는 일이 아니라 내 작업장을 AI에게 맞게 정리하는 일에 가깝습니다. 어디서부터 시작해야 할지 모르겠다면, 카파시처럼 빈 마크다운 파일 하나를 만드시기를 추천드립니다. 거기에 매일 한 줄씩, 내가 AI에게 시키고 싶은 일과 그때마다 AI가 빠뜨리던 것을 적으시면 됩니다. 한 달이 지나면 그 파일이 첫 번째 하네스가 되어 있을 것입니다. 그게 우리가 모두 추구하고 있는 길이고, 학생들에게도 길러 주어야 할 역량이라고 생각합니다.

이 프로젝트에 대해 팀과 소통하기