이 모듈은 새 알고리즘이 아니라 패턴 서베이예요. 목표는 두 가지 — 에이전트 시스템의 모양을 알아보는 눈과, 그걸 설명할 공통 어휘.
앞으로 나올 9가지(루프 · Claude Code · 서브에이전트 · 메모리 · 라우팅 · 캐싱 · 롱러닝 · 컴퓨터 유즈 · 안전)는
전부 한 개의 루프 위에 얹거나 그 루프에 도구를 더 꽂는 레이어일 뿐입니다. 먼저 그 지도를 보고,
용어부터 손에 익혀요 — 뒤 섹션들이 이 단어들을 계속 다시 씁니다.
Everything in this module wraps one core loop
flowchart TB
subgraph WRAP["the harness — everything else"]
direction LR
SUB["subagents"]
MEM["memory scopes"]
ROUTE["model routing"]
CACHE["prompt caching"]
ASYNC["long-running / async"]
CU["computer use"]
SAFE["safety: gates + limits"]
end
WRAP --> CORE["the agent loop ~20 lines"]
classDef core fill:#11271a,stroke:#3fb950,color:#e6edf3,stroke-width:2px;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef data fill:#2a2410,stroke:#d29322,color:#e6edf3;
classDef danger fill:#2d1215,stroke:#f85149,color:#e6edf3;
class SUB,MEM,CU io;
class ROUTE,CACHE,ASYNC data;
class SAFE danger;
class CORE core;
핵심 어휘 — 5묶음으로
엔진:agent(루프로 도구를 부르는 프로그램) · agentic loop(모델→도구→결과→반복) · harness(루프 둘레의 통제 코드) · tool registry(쓸 수 있는 도구 목록) · dispatcher(도구 이름 → 실제 함수 연결)
위임:subagent(자식 Claude 세션) · task tool(서브에이전트를 띄우는 도구) · context isolation(자식 작업이 부모 창을 안 더럽힘)
통제:permission gate(도구 실행 전 허용/확인/차단) · hook(도구 호출 전후에 끼우는 자동 훅)
비용·성능:router(난이도별 모델 선택) · fallback(1차 실패 시 2차) · ensemble(여럿 돌려 최선 택) · cache_control: ephemeral(정적 prefix 5분 캐시)
비전:computer use(화면을 API처럼 다룸) · vision tool(스크린샷을 보고 다음 행동 결정)
🗺️ 이 모듈을 관통하는 한 문장: "에이전트는 새 모델이 아니라 루프 + 하니스다."
그래서 지능은 모델에, 통제는 하니스에 — 이 배치를 모든 패턴에서 반복해서 확인하게 됩니다.
이 모듈 전체를 관통하는 핵심 주장은?
9가지 패턴은 전부 같은 ~20줄 루프 위의 레이어예요. 그래서 "모양 알아보기"가 목표죠 —
새로운 시스템을 만나도 "아, 이건 루프에 메모리를 붙이고 / 도구를 더 꽂고 / 게이트를 씌운 것"으로
환원해서 읽을 수 있게 됩니다.
Claude에 messages+tools 전송 → tool call 디스패치 → 결과 append → end_turn까지 반복.
The whole engine — a single while-loop
flowchart LR
A["messages + tools to Claude"] --> B{"stop_reason?"}
B -->|tool_use| C["run the tool"]
C --> D["append result to messages"]
D --> A
B -->|end_turn| E["done"]
classDef core fill:#11271a,stroke:#3fb950,color:#e6edf3,stroke-width:2px;
classDef decision fill:#241a2e,stroke:#a371f7,color:#e6edf3;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef done fill:#11271a,stroke:#3fb950,color:#e6edf3;
class A core;
class B decision;
class C,D io;
class E done;
messages = [{"role": "user", "content": question}]
while True:
reply = client.messages.create(model=..., tools=TOOLS, messages=messages)
messages.append({"role": "assistant", "content": reply.content})
if reply.stop_reason != "tool_use":
break # end_turn → 끝
for call in tool_calls(reply): # 모델이 요청한 도구들 실행
result = dispatch(call.name, call.input)
messages.append({"role": "user",
"content": [{"type": "tool_result",
"tool_use_id": call.id, "content": result}]})
Module 3의 Level 3에서 본 바로 그 루프예요. 이 ~20줄이 엔진이고, 화려한
에이전트는 모두 이 위에 UI·권한·도구를 덧댄 것뿐입니다.
🧩 "thick harness"가 뭔가: 엔진(loop)은 얇습니다. 진짜 제품이 되려면 그 둘레에
도구 레지스트리 · 권한 게이트 · 메모리 · 에러 복구 · UI를 둘러야 해요. 이 둘레가 두꺼울수록
"프레임워크"가 됩니다. 핵심: 지능은 모델에, 통제는 하니스에 있습니다.
"agent"의 가장 정확한 정의는?
에이전트는 새 모델이 아니라 제어 구조예요. "모델 호출 → 도구 실행 → 결과 append →
반복"이라는 루프가 본질이고, 모델은 매 턴 "다음에 뭘 할지"만 정합니다. 그래서 ~20줄로 충분합니다.
제한된 도구셋·전용 system prompt로 자식 Claude 세션을 띄움 → 컨텍스트 격리·전문화.
부모는 자식이 한 일의 요약만 받습니다 → 병렬 처리 + 재귀적 분해. 큰 일을 작은 독립 작업으로
쪼개 여럿에게 동시에 맡기는 셈이에요(42의 fork()/프로세스 분할과 비슷한 감각).
One parent fans work out, gets summaries back
flowchart TB
P["parent agent full context"] --> C1["subagent A narrow tools + own prompt"]
P --> C2["subagent B narrow tools + own prompt"]
P --> C3["subagent C narrow tools + own prompt"]
C1 -.summary only.-> P
C2 -.summary only.-> P
C3 -.summary only.-> P
classDef core fill:#11271a,stroke:#3fb950,color:#e6edf3,stroke-width:2px;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
class P core;
class C1,C2,C3 io;
🪟 왜 "요약만" 돌려받나 — 컨텍스트 보호: 자식이 파일 50개를 읽어도 부모 창에는
결론 한 단락만 들어옵니다. 덕분에 부모의 컨텍스트가 잡음으로 차오르지 않아요. 이게 서브에이전트의
진짜 이득 — 병렬성보다 컨텍스트 격리가 더 큽니다.
서브에이전트를 쓰는 가장 큰 이점은?
서브에이전트는 컨텍스트 격리 + 전문화가 핵심이에요. 넓은 탐색은 자식에게 맡기고
부모는 결론만 받아 큰 그림을 유지합니다. 토큰은 오히려 더 쓰지만(여러 세션), 부모 창이 안 더러워지는
대가로 충분히 남는 장사죠.
"무엇을 어디까지 기억하나"를 5단계 스코프로 나눕니다 — 좁은 휘발성부터 넓은 영속까지.
Memory Scopes (5)
conversation — 현재 세션의 메시지들 (가장 좁고 휘발성)
session — 로그인 지속 동안 유지
user — 사용자별 영속 선호 (재방문해도 남음)
project — 레포 컨벤션 (CLAUDE.md 같은 파일)
world — 벡터 스토어·지식베이스 (= Module 4의 RAG, 가장 넓음)
Five scopes, narrow & volatile to wide & persistent
flowchart LR
A["conversation this session"] --> B["session while logged in"]
B --> C["user persistent prefs"]
C --> D["project repo conventions (CLAUDE.md)"]
D --> E["world vector store / knowledge base"]
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef data fill:#2a2410,stroke:#d29322,color:#e6edf3;
class A,B,C io;
class D,E data;
🔁 2편과 4편이 여기서 만난다: 2편에서 "API는 stateless"라 했죠 — 이 스코프들이 바로
그 stateless 위에 우리가 얹는 기억 장치입니다. 그리고 가장 넓은 world 스코프는
새 개념이 아니라 Module 4의 RAG를 "기억"에 그대로 적용한 거예요.
가장 넓은 world 스코프는 어떻게 구현될까요?
world 메모리 = RAG를 기억에 적용한 것이에요. 사실을 chunk·embed해 저장하고,
필요할 때 top-K로 검색해 붙입니다. 모델 가중치는 학습 때 고정되고, context window 무한 확장은
Module 4 첫 섹션의 4가지 이유(Scale·Cost·Latency·Accuracy)에 부딪혀요.
Multi-Model Routing — 싼 모델(Haiku)을 분류기로 써서 적절한 모델로 라우팅 → 비용 한정
Prompt Caching — 정적 system prefix를 cacheable로 표시 → 5분 윈도우 내 반복 비용 ~90%↓
Long-Running Tasks — 백그라운드 잡·예약 wake-up·webhook으로 비동기. 일시정지 중 상태를 어디 둘지 결정
Progress Streaming — 각 단계서 사람이 읽을 이벤트 emit, 전송계층과 분리, 종료는 done/error
Multi-model routing — a cheap model triages first
flowchart LR
Q(["incoming request"]) --> H["cheap model (Haiku) classify difficulty"]
H -->|simple| S["answer with Haiku fast, cheap"]
H -->|hard| B["escalate to a bigger model (Sonnet / Opus)"]
classDef core fill:#11271a,stroke:#3fb950,color:#e6edf3,stroke-width:2px;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef done fill:#11271a,stroke:#3fb950,color:#e6edf3;
classDef data fill:#2a2410,stroke:#d29322,color:#e6edf3;
class H core;
class Q io;
class S done;
class B data;
💸 Prompt caching, 한 번 더: 매 요청에 똑같이 들어가는 긴 system 프롬프트·도구
정의를 캐시로 표시하면, 5분 윈도우 안의 다음 호출들은 그 부분을 재처리하지 않고
재사용해요. 같은 토큰을 매번 새로 계산하지 않으니 반복 비용이 ~90%까지 떨어집니다. 정적인 부분은
앞에, 변하는 부분은 뒤에 두는 게 캐시 적중의 핵심.
Long-running tasks — pause, persist, resume
flowchart LR
T["long task starts"] --> Wk["do a chunk of work"]
Wk --> P{"need to wait? API, human, timer"}
P -->|no| Wk
P -->|yes| Sv["persist state, release the worker"]
Sv -.webhook / scheduled wake-up.-> Wk
classDef core fill:#11271a,stroke:#3fb950,color:#e6edf3,stroke-width:2px;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef decision fill:#241a2e,stroke:#a371f7,color:#e6edf3;
classDef data fill:#2a2410,stroke:#d29322,color:#e6edf3;
class Wk core;
class T io;
class P decision;
class Sv data;
멀티모델 라우팅에서 값싼 Haiku를 "분류기"로 먼저 두는 이유는?
라우팅의 목적은 비용 상한이에요. 대부분의 요청은 쉬운데, 그걸 전부 Opus로 처리하면
낭비죠. 싼 모델이 먼저 난이도를 보고, 진짜 어려운 것만 비싼 모델로 escalate 합니다.
prompt caching 효과를 극대화하려면 프롬프트를 어떻게 배치해야 할까요?
캐시는 접두(prefix) 단위로 적중해요. 앞부분이 매번 똑같아야 그 구간을 재사용합니다.
그래서 정적인 system·도구 정의를 앞에 고정하고, 변하는 사용자 입력을 뒤로 미는 게 핵심이에요.
flowchart LR
subgraph ENV["Environment — you own it"]
VM["container / VM Linux desktop"]
end
subgraph APP["Your harness — the loop"]
CAP["capture screenshot (PNG)"]
EXE["execute action via xdotool"]
end
subgraph API["Anthropic API"]
CL["Claude decides the next action from pixels"]
end
VM --> CAP --> CL --> EXE --> VM
classDef data fill:#2a2410,stroke:#d29322,color:#e6edf3;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef core fill:#11271a,stroke:#3fb950,color:#e6edf3,stroke-width:2px;
class VM data;
class CAP,EXE io;
class CL core;
Claude는 당신 기계를 절대 만지지 않아요 — 결정만 합니다.
스크린샷을 보내면 action이 담긴 tool_use를 돌려주고, 그걸 당신의 하니스가
xdotool로 실행 → 다시 스크린샷 → Claude가 도구를 그만 부를 때까지 반복. 5A.1의 그 루프와
똑같되 "도구"가 마우스·키보드일 뿐이에요.
⚖️ 대가를 알고 쓰기: 매 턴 스크린샷이라 느리고 vision 토큰을 많이 먹고, UI가
조금만 바뀌어도 깨지기 쉽습니다(brittle). 원칙: API나 accessibility tree가 있으면 그걸 먼저,
computer use는 "다른 길이 없을 때"의 최후 수단.
🛡️ Safety = 권한 게이트 + 하드 리밋. 읽기는 항상 허용, 파괴적 작업은 확인,
실행은 샌드박스. 그리고 스텝 예산·토큰 캡·시간 데드라인·scope 제한으로 폭주를 막습니다.
Two layers of defense — gate every action, cap the whole run
flowchart TB
A["agent proposes an action"] --> G{"permission gate"}
G -->|read| OK["allow"]
G -->|write / delete| Ask["ask human first"]
G -->|exec| Sand["run in sandbox"]
OK --> L{"hard limits steps, tokens, time"}
Ask --> L
Sand --> L
L -->|within budget| Next["continue loop"]
L -->|exceeded| Stop["halt the run"]
classDef decision fill:#241a2e,stroke:#a371f7,color:#e6edf3;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef done fill:#11271a,stroke:#3fb950,color:#e6edf3;
classDef danger fill:#2d1215,stroke:#f85149,color:#e6edf3;
class A,Ask,Sand io;
class G,L decision;
class OK,Next done;
class Stop danger;
에이전트가 무한 루프로 비용 폭탄이 되는 걸 막는 1차 방어선은?
능력(자율성)과 안전은 트레이드오프예요. 모델을 믿기보다, 코드로 강제하는 한도가
폭주를 막습니다. 파괴적 작업엔 사람 확인(human-in-the-loop)도 함께.
권한 게이트에서 "읽기/쓰기/실행"을 다르게 다루는 이유는?
핵심은 되돌릴 수 있는가예요. 읽기는 부작용이 없으니 자유롭게, 파일 삭제·셸 실행은
돌이키기 어렵고 외부에 영향을 주니 확인·샌드박스로 감쌉니다. 위험도에 비례한 마찰이 좋은 설계죠.
5A.7 · Project: build a data-analyst agent Slide 17–21
SQLite 위 read-only SQL 도구 3개로 agent loop를 시연하는 스타터(안전 패턴 2개 기구현).
Safety patterns already in the starter
구조적으로 read-only인 DB(안전 패턴 1) — 쓰기 도구를 아예 안 줌
max-steps 예산(안전 패턴 2) — 루프가 N스텝 넘으면 강제 종료
The data-analyst loop you'll wire up
flowchart LR
U(["natural-language question"]) --> Ag["agent loop"]
Ag -->|list_tables| DB[("read-only SQLite")]
Ag -->|describe_table| DB
Ag -->|run_query| DB
DB -.rows.-> Ag
Ag --> Ans(["answer in plain language"])
classDef core fill:#11271a,stroke:#3fb950,color:#e6edf3,stroke-width:2px;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef data fill:#2a2410,stroke:#d29322,color:#e6edf3;
classDef done fill:#11271a,stroke:#3fb950,color:#e6edf3;
class Ag core;
class U io;
class DB data;
class Ans done;
코드 읽기 ① — 함수 이름 지도 (전체 모양부터)
스타터(agent-app.zip)의 런타임은 파일 둘 — agent.py(루프+CLI) ·
tools.py(도구 3개 + 라우터). 함수는 7개지만 모든 길은 answer_turn() 하나로
모여요. 아래 지도가 재생되며 하나씩 나타납니다(우측 위 ▶ 다시 재생).
flowchart TD
MAIN["__main__"] -->|arg| A1["answer_turn(messages)"]
MAIN -->|no arg| REPL["repl()"]
REPL --> A1
A1 --> CREATE["messages.create() SYSTEM + messages + TOOLS"]
CREATE --> CHECK{"tool_use block?"}
CHECK -->|no| RET["return final text"]
CHECK -->|yes| DISP["dispatch(name, input)"]
DISP --> APP["append tool_result"]
APP --> CREATE
DISP --> LT["list_tables()"]
DISP --> DT["describe_table(table)"]
DISP --> RQ["run_query(sql)"]
LT --> CONN["_connect() · mode=ro"]
DT --> CONN
RQ --> CONN
classDef core fill:#11271a,stroke:#3fb950,color:#e6edf3,stroke-width:2px;
classDef io fill:#0d1f33,stroke:#58a6ff,color:#e6edf3;
classDef decision fill:#241a2e,stroke:#a371f7,color:#e6edf3;
classDef data fill:#2a2410,stroke:#d29322,color:#e6edf3;
classDef done fill:#11271a,stroke:#3fb950,color:#e6edf3;
class A1 core;
class CHECK decision;
class CONN data;
class RET done;
class MAIN,REPL,CREATE,DISP,APP,LT,DT,RQ io;
엔진 = answer_turn() — 네 동작의 반복
def answer_turn(messages: list) -> str:
for _ in range(MAX_STEPS): # 스텝 예산 = 폭주 방지
resp = client.messages.create(model=MODEL, system=SYSTEM,
messages=messages, tools=TOOLS)
messages.append({"role": "assistant", "content": resp.content})
tool_calls = [b for b in resp.content if b.type == "tool_use"]
if not tool_calls: # 도구 안 부름 → 끝(end_turn)
return "".join(b.text for b in resp.content if b.type=="text").strip()
results = [{"type": "tool_result", "tool_use_id": c.id,
"content": dispatch(c.name, c.input)} for c in tool_calls]
messages.append({"role": "user", "content": results}) # 결과 되먹임
묻기 → (도구 없으면 종료) → dispatch로 실행 → 결과 append → 반복.
출구는 딱 둘: 도구를 안 부를 때(정상 end_turn) /
MAX_STEPS 소진(안전 정지).
코드 읽기 ② — 실제 질문이 루프를 도는 모습
질문 "지난 분기 매출 top 3 제품?" — 데이터 최신일(2026-05-31) 기준 "지난 분기" = 2026 Q1.
루프가 5바퀴 돕니다(예산 12 중).
sequenceDiagram
participant L as answer_turn() loop
participant API as messages.create()
participant DB as data.db (mode=ro)
Note over L: 1 · orient
L->>API: create(messages, TOOLS)
API-->>L: tool_use list_tables
L->>DB: dispatch to list_tables()
DB-->>L: customers, order_items, orders, products
Note over L: 2 · schema
L->>API: create()
API-->>L: tool_use x3 describe_table
L->>DB: dispatch to describe_table()
DB-->>L: columns + sample rows
Note over L: 3 · find "now"
L->>API: create()
API-->>L: tool_use run_query MAX(order_date)
L->>DB: dispatch to run_query()
DB-->>L: 2026-05-31
Note over L: 4 · the real query
L->>API: create()
API-->>L: tool_use run_query top-3 revenue Q1
L->>DB: dispatch to run_query()
DB-->>L: Robot Vacuum / Cookware Set / Scented Candle
Note over L: 5 · answer
L->>API: create()
API-->>L: end_turn (text, no tool_use)
Note over L: return final answer
🔎 루프의 모양은 코드가, 루프 안의 행동은 프롬프트가 정합니다.
1·2바퀴가 늘 스키마 탐색인 건 코드가 아니라 SYSTEM 프롬프트가 "먼저 list_tables → describe_table 하라"고
시켜서예요. 그리고 messages는 매 바퀴 tool_use + tool_result 두 블록씩 자라며 —
이게 곧 멀티턴 기억(5A.4)입니다.
🚀 Beyond — Managed Agents (beta, 슬라이드 23): 소유 모델 역전 — Anthropic이
루프를 실행하고, 당신은 도구 정의 + 이벤트 스트림 반응만 합니다. 수동 메시지 리스트 관리가 사라지고
멀티턴 세션이 무료로 제공돼요.
이 데이터분석 에이전트에 run_query(SELECT 전용)만 주고 run_sql(임의 실행)을 안 주는 이유는?
가장 튼튼한 안전장치는 위험한 도구를 애초에 안 주는 것이에요(안전 패턴 1). 모델이
아무리 헷갈려도 줄 수 있는 도구가 SELECT뿐이면 DROP/UPDATE는 불가능합니다. 프롬프트 경고보다
확실하죠.