프레임워크에 갇히지 말자. 좋은 에이전트는 80%까지는 쉽지만, 그 위는 모두 "직접 만든 소프트웨어"다.
12가지 원칙을 보기 전에 깔아둬야 하는 기본 개념.
요즘 회사들이 "AI 에이전트"라고 부르는 제품의 대부분은 사실 대부분 결정론적 코드다. 그 사이사이에 LLM 호출이 양념처럼 끼어 있을 뿐이다. 진짜 에이전트도 그 본질은 다르지 않다. "프롬프트 주고, 도구 한 보따리 던져주고, 목표 달성까지 무한 루프" 같은 동화 속 패턴은 80%까지만 작동한다.
한 문장으로 줄이면.
2011년 Heroku가 발표해 클라우드 시대 웹앱의 표준이 된 12-Factor App 원칙을 LLM 에이전트 시대에 다시 쓴 가이드다. 코드 한 줄짜리 도구를 깔아주는 레포가 아니라, "이런 식으로 설계하라"라는 12개의 짧은 글 모음이다. TypeScript 80%, Python·Jupyter 19%로 구성된 예제 코드가 곁들여진다.
저자는 HumanLayer를 만드는 Dex Horthy. CrewAI, LangChain, smolagents, LangGraph, Griptape 등 시중 에이전트 프레임워크를 거의 다 써본 뒤 "YC 안팎의 진지한 창업자들 대부분이 결국 프레임워크를 버리고 스택을 직접 만든다"는 관찰에서 출발했다.
시대적 배경 + 경쟁 자료 대비 차별점.
2025년부터 "컨텍스트 엔지니어링(Context Engineering)"이라는 용어가 업계의 새 화두로 떠올랐다. 안드레이 카르파시(Andrej Karpathy), 토비 뤼트케(Shopify CEO) 같은 인물들이 트위터(X)에서 이 용어를 사용하며 12-factor-agents의 Factor 3(컨텍스트 윈도우를 직접 소유하라)이 사실상 "컨텍스트 엔지니어링의 원전" 자리에 올랐다.
| 자료 | 스타일 | 한계 / 차이 |
|---|---|---|
| Anthropic "Building Effective Agents" | 추상 패턴 분류 | 구체적 코드·안티패턴 부족 |
| LangChain · LlamaIndex 공식 문서 | "이 함수를 쓰세요" | 프레임워크에 종속 |
| OpenAI Agents SDK | SDK 사용법 | 특정 회사 제품에 묶임 |
| 12-factor-agents | 코드 + 안티패턴 짝지음 | "함수를 쓰지 말고 이 원칙대로 직접 만들어라" |
대부분 팀의 여정은 동일하다. ① 에이전트 만들기로 결심 → ② UX 매핑 → ③ 빨리 가려고 프레임워크 도입 → ④ 70~80% 품질 달성 → ⑤ 마지막 20%가 안 됨 → ⑥ 프레임워크 내부를 역공학 → ⑦ 결국 처음부터 다시 작성. 이 패턴을 "프로덕션 80% 벽"으로 명명하고 거기 부딪힌 사람들을 정확히 겨냥한다.
저자가 만난 100명 이상의 SaaS 창업자에게 공통적으로 효과적이었던 방식은 "그린필드 재작성" 대신 "기존 제품에 작은 모듈 단위로 에이전트 패턴을 끼워넣기"였다. 12-factor는 그 모듈의 카탈로그 역할을 한다.
TypeScript와 Python 예제를 모두 제공하지만 원칙 자체는 언어·LLM 벤더 독립. OpenAI든 Anthropic이든 Google이든 같은 12가지 압력에 부딪힌다는 게 핵심 가정.
백엔드 · 트리거 채널 · 인프라.
이 레포는 "프레임워크"가 아니라 "가이드"라서 의존성이 가볍다. 하지만 워크숍 코드와 패키지가 들어있어 실제 기술 스택은 명확히 드러난다.
4줄짜리 의사 코드 + 12개 원칙을 4그룹으로.
저자가 그리는 에이전트의 본질은 4줄짜리 의사 코드다.
initial_event = {"message": "..."}
context = [initial_event]
while True:
next_step = await llm.determine_next_step(context)
context.append(next_step)
if next_step.intent == "done":
return next_step.final_answer
result = await execute_step(next_step)
context.append(result)
이 단순한 루프에 12가지 압력이 가해진다. 각 압력에 어떻게 대응할지가 12개의 원칙이다.
┌────────────────────────────────────────────────────────────┐ │ 외부 트리거 (Slack / Email / Cron / Webhook / Manual) │ │ ↓ Factor 11 │ │ ┌───────────────────────────────────────────────────┐ │ │ │ Stateless Reducer (Factor 12) │ │ │ │ │ │ │ │ Thread = [event_0, event_1, ..., event_n] │ │ │ │ nextEvent = f(thread) ← LLM이 결정 │ │ │ │ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ Context Window (Factor 3) │ │ │ │ │ │ - system prompt (Factor 2) │ │ │ │ │ │ - tool schemas (Factor 4) │ │ │ │ │ │ - thread history │ │ │ │ │ │ - compact errors (Factor 9) │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ │ ↓ │ │ │ │ LLM → Structured JSON (Factor 1, 4) │ │ │ │ ↓ │ │ │ │ Tool Executor (deterministic code) │ │ │ │ ↓ ↓ │ │ │ │ 자동 실행 사람 호출 │ │ │ │ (DB / API / Code) (Factor 7) │ │ │ │ ↓ ↓ │ │ │ │ Result Event Human Response │ │ │ │ └──────────────┬───────────────┘ │ │ │ │ ↓ │ │ │ │ Unified State Store (Factor 5) │ │ │ │ - Pause/Resume API (Factor 6) │ │ │ │ - Own control flow (Factor 8) │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ ※ 한 에이전트 = 작고 집중된 역할 (Factor 10) │ └────────────────────────────────────────────────────────────┘
리듀서(Reducer)는 React Redux를 써본 사람에게 친숙하다. (이전 상태, 액션) → 새 상태 함수다. Factor 12는 에이전트를 그 모양으로 보라고 한다. 스레드(이벤트 목록)와 새 이벤트를 받아 다음 이벤트를 결정하는 순수 함수. 함수 자체에는 기억이 없고, 기억은 바깥의 스레드 저장소가 가진다. 그래서 정전이 나도, 서버를 죽였다 살려도, 같은 스레드를 다시 넣으면 똑같이 재현된다.
가장 흔한 첫 시도. 80%까지는 잘 굴러간다. 그러나 ① 같은 도구를 무한 반복 호출 ② 컨텍스트가 부풀어 토큰 한도 초과 ③ 사용자가 중간 개입을 못 함 ④ 에러 복구 불가 — 이 네 가지가 거의 동시에 터진다.
"다음 단계를 결정"하는 LLM 호출을 한 번에 한 단계만 시키고, 도구 실행과 분기·재시도는 결정론적 코드가 소유. LLM이 단순 분류기 역할을 하도록 좁히면 디버깅·테스트가 일반 백엔드 수준으로 떨어진다.
코드 레포가 아니라 책 레포.
humanlayer/12-factor-agents/ ├── content/ # 12개 Factor 본문 마크다운 (가이드의 본체) │ ├── brief-history-of-software.md │ ├── factor-01-natural-language-to-tool-calls.md │ ├── factor-02-own-your-prompts.md │ ├── factor-03-own-your-context-window.md ← 컨텍스트 엔지니어링 원전 │ ├── factor-04-tools-are-structured-outputs.md │ ├── factor-05-unify-execution-state.md │ ├── factor-06-launch-pause-resume.md │ ├── factor-07-contact-humans-with-tools.md │ ├── factor-08-own-your-control-flow.md │ ├── factor-09-compact-errors.md │ ├── factor-10-small-focused-agents.md │ ├── factor-11-trigger-from-anywhere.md │ ├── factor-12-stateless-reducer.md │ └── appendix-13-pre-fetch.md ← 보너스 원칙 ├── workshops/ # 단계별 워크숍 코드 (가장 실습 가치 높음) ├── packages/ # 재사용 가능한 코드 예제 패키지 ├── drafts/ # 추가 원칙·아이디어 드래프트 ├── hack/contributors_markdown/ # 기여자 자동 표기 빌드 스크립트 ├── img/ # 가이드 본문에 쓰이는 다이어그램 이미지 ├── CLAUDE.md # Claude Code용 레포 설명(컨트리뷰터에 친절) ├── Makefile # 빌드/렌더 자동화 └── README.md # 12-factor의 입구
분야별로 무엇을 손에 잡을지.
Thread = { events: Event[] }, Event = { type, data }로 시작가이드 자체는 GitHub에서 그냥 읽으면 된다.
| 구분 | 도구 | 용도 |
|---|---|---|
| 필수 런타임 | Node.js 18+ / pnpm 또는 npm | TypeScript 워크숍 다수 |
| 필수 런타임 | Python 3.11+ (pip 또는 uv) | Jupyter 노트북 예제용 |
| 필수 키 | OpenAI 또는 Anthropic API 키 | LLM 호출 (무료 크레딧 또는 소액) |
| 필수 도구 | Git | 레포 클론 |
| 권장 | BAML CLI | Factor 1~4를 코드로 익히기 좋은 DSL |
| 권장 | 로컬 LLM (Ollama) | 토큰 비용 걱정 없이 컨텍스트 포맷 실험 |
| 권장 | VS Code + Copilot 또는 Claude Code | 가이드 따라 미니 에이전트 짤 때 |
| 권장 | Slack 워크스페이스 (무료) | Factor 11 트리거 실험 |
전반적으로 로컬 노트북 한 대 + API 키 한 개면 모든 원칙을 손으로 확인해볼 수 있다. GPU·서버는 필요 없다.
난이도별 — Easy → Hard.
1) Factor 1을 따라, 자연어 한 줄을 받아 { "intent": "send_email", "to": "...", "subject": "..." } 같은 JSON으로 변환하는 LLM 호출 작성 2) 도구는 실제로 실행하지 말고 콘솔에 찍기만 함 3) 30분짜리 과제
예시 입력: "오늘 회의록 정리해서 팀에 메일 보내줘" → tool: send_email, recipients: team, body: ...
Factor 3 따라, OpenAI 표준 messages 배열 대신 <slack_message>, <tool_call> 같은 태그로 같은 정보를 한 user 메시지에 욱여넣기. 토큰 수와 응답 품질 비교.
배우는 것: 컨텍스트 포맷이 정확도에 미치는 영향
Factor 9. 의도적으로 실패하는 도구(throw new Error("DB connection refused"))를 만들고, 그 에러 메시지를 컨텍스트에 1~2줄로 줄여 넣은 뒤 LLM이 다음 단계로 무엇을 제안하는지 관찰.
배우는 것: 자가 회복 패턴, "ROI 가장 높은 30분 실험"
Factor 7 구현. ask_human 도구를 정의하고, LLM이 이걸 호출하면 콘솔에서 사용자 입력을 받아 결과를 컨텍스트에 다시 붙이기. 실제 서비스라면 Slack DM이나 이메일로 대체.
배우는 것: HITL 패턴, 사람을 함수처럼 다루기
Factor 6 + Factor 12. Thread를 SQLite나 Redis에 저장하고, HTTP API 2개(POST /agent/start, POST /agent/resume/:id)만 노출. 서버를 죽였다 살려도 같은 ID로 재개되면 통과.
배우는 것: 영속화, 상태 외부화, 재현 가능성
이미 LLM을 쓰고 있다면 가장 위험한 곳(루프 안에서 LLM 부르는 곳)을 골라 ① 프롬프트 분리(Factor 2) ② 컨텍스트 포맷 직접 작성(Factor 3) ③ 제어 흐름 회수(Factor 8) 3종 세트 적용. Before/After 토큰 수와 정확도 비교.
배우는 것: 12-factor 적용 ROI를 자기 코드로 체감
Factor 10. "이메일 분류 → 요약 → 회신 초안" 같은 워크플로를 3개의 작은 에이전트로 쪼개고, 각 에이전트의 컨텍스트 윈도우를 독립적으로 관리. 한 에이전트가 다른 에이전트를 "도구"로 호출하게.
배우는 것: 합성(composition), 단일 책임 원칙
Factor 11 + 1~12 종합. Slack 슬래시 명령으로 시작 → 도구 호출 → 사람 승인(Factor 7) → 배포 → 결과 보고. 가장 현실적인 미니 프로덕트.
배우는 것: 종합적인 12-factor 적용, 실 사용 시나리오
12-factor에 직접 언급은 짧지만, 작은 에이전트들의 신뢰성은 결국 자동 평가 없이는 측정 불가. 본인 에이전트에 대해 ① 기대 출력 데이터셋 ② 자동 평가 스크립트 ③ regression 감지 추가.
배우는 것: LLM 시스템의 회귀 테스트, 객관적 품질 측정
주차별로 따라가면 12개 원칙 + 주변 기술 마스터.
README + "Brief History of Software" 정독 → Anthropic "Building Effective Agents"와 비교 독서 → "에이전트"라는 단어 없이 시스템을 DAG/이벤트 루프로 설명해보기
Factor 1, 2, 3, 4 본문 + 실습 1, 2번 → OpenAI Function Calling vs JSON Mode vs Structured Outputs 비교 글 읽기 → BAML 튜토리얼 1개 따라하기
Factor 5, 6 본문 + 실습 5번 → Temporal 또는 Inngest 무료 튜토리얼 1개 → SQLite로 Thread 저장소 직접 구현
Factor 7, 8, 9 본문 + 실습 3, 4번 → HumanLayer 또는 자체 구현으로 ask_human 도구 작성
Factor 10, 11, 12 + 부록 13 정독 → 실습 7번(멀티 에이전트) 도전 → "Stateless Reducer"를 Redux/Elm 아키텍처와 비교
실습 6번 — 본인 프로젝트의 LLM 호출 리팩토링 → Slack 봇 또는 이메일 트리거 추가(Factor 11)
실습 9번 — eval 파이프라인 추가 → LangSmith·Langfuse·Helicone 등 LLM 관측 도구 중 1개 도입
실습 8번(Slack 봇) 또는 본인 프로젝트의 Before/After를 사내 발표 → 12-factor-agents에 한국어 번역·예제·이슈로 기여
레포를 읽다 막힐 만한 단어들.
nextEvent = agent(thread, lastEvent) 형태로 에이전트를 순수 함수로 본다. 같은 입력 → 같은 출력 → 재현·테스트·디버깅 쉬워짐.레포 · 강연 · 관련 표준.
<slack_message>·<tool_call> 태그로 한 user 메시지에 욱여넣고 토큰 수·정확도를 비교. 컨텍스트 엔지니어링의 첫 손맛.