HANDS-ON LAB / CHAPTER 6
Claude Code Deep Dive Workshop, Chapter 6 - Agent SDK 실습
지금까지는 터미널에서 Claude Code와 대화했습니다. 이 랩에서는 같은 에이전트 루프를 여러분의 프로그램 안으로 가져옵니다. query 한 번으로 도구를 쓰는 에이전트를 호출하고, session을 이어 대화 기억을 만들고, 내 함수를 커스텀 도구로 쥐여 주고, 프로세스가 죽어도 기억을 이어 가는 미니 상주 점검원까지 60분에 완주합니다.
소요 시간 60분 내외 · Task 준비 + 4개 · SDK @anthropic-ai/claude-agent-sdk · Update 2026.07
Agent SDK는 Claude Code CLI를 자식 프로세스로 구동합니다. 그래서 설치만 하면 Chapter 1의 인증과 Chapter 3의 Bedrock 설정을 그대로 상속합니다. 새 자격증명이 필요 없습니다.
Terminal, 전체 복사
node -v # 18 이상, 랩 EC2는 22 mkdir -p ~/agentlab && cd ~/agentlab npm init -y > /dev/null npm install @anthropic-ai/claude-agent-sdk zod claude /status # CLI 인증이 살아 있으면 SDK도 그대로 동작
Bedrock 경로 사용자는 Chapter 3에서 설정한 환경변수 두 줄이 이 셸에 살아 있는지만 확인하세요: CLAUDE_CODE_USE_BEDROCK=1과 ANTHROPIC_MODEL=global.anthropic.claude-sonnet-4-6.
격리 기본값
SDK는 기본적으로 파일시스템 설정(settingSources)을 로드하지 않습니다.
CLAUDE.md나 프로젝트 설정 없이 깨끗한 상태로 시작하는 것이 기본이고, 필요할 때만
settingSources: ["user", "project"]처럼 명시해 불러옵니다.
임베딩된 에이전트가 예측 가능해야 하기 때문입니다.
CHECKPOINT
claude -p로 하던 일을 query() 함수로 합니다. 스트리밍 메시지를 순회하며 Chapter 5에서 본 그 JSON 필드들(session_id, num_turns, total_cost_usd)이 타입 있는 객체로 돌아오는 것을 확인합니다.
Terminal, 전체 복사 (~/agentlab에서)
cat > hello.mjs << 'HELLOEOF'
// T1: 첫 query, CLI의 프로그래매틱 쌍둥이
import { query } from "@anthropic-ai/claude-agent-sdk";
const q = query({
prompt: "이 디렉토리의 파일 목록을 보고 한 줄 소감을 말해줘",
options: { allowedTools: ["Read", "Glob"], maxTurns: 3 },
});
for await (const m of q) {
if (m.type === "assistant") {
for (const b of m.message.content) if (b.type === "text") process.stdout.write(b.text);
}
if (m.type === "result") {
console.log(`\n--- session=${m.session_id} turns=${m.num_turns} cost=$${m.total_cost_usd}`);
}
}
HELLOEOF
node hello.mjs
출력 예시
package.json과 node_modules뿐인 갓 태어난 프로젝트네요. 시작이 반입니다. --- session=ab12cd34-... turns=2 cost=$0.0031
allowedTools: ["Read", "Glob"]가 이 에이전트의 손발 전부입니다. Bash를 주지 않았으니 파일 실행은 불가능합니다. Chapter 5의 --allowed-tools와 같은 개념이 옵션 객체 한 줄이 된 것뿐입니다.
assistant 메시지의 실제 모양
스트림의 assistant 메시지는 m.message.content 안에 블록 배열로 들어 있습니다.
text 블록만 골라 출력하는 위 패턴을 그대로 쓰세요. 최종 텍스트만 필요하면
result 메시지의 m.result가 가장 간단합니다.
CHECKPOINT
query는 기본적으로 매번 새 대화입니다. 첫 응답의 session_id를 붙잡아 다음 query의 resume에 넘기면 대화가 이어집니다. Chapter 5의 --resume과 같은 원리를 코드 3줄로 구현합니다.
Terminal, 전체 복사
cat > chat.mjs << 'CHATEOF'
// T2: 대화 메모리, session_id를 resume으로 잇는다
import readline from "node:readline/promises";
import { query } from "@anthropic-ai/claude-agent-sdk";
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
let sessionId;
while (true) {
const line = (await rl.question("나> ")).trim();
if (!line || line === "exit") break;
const q = query({
prompt: line,
options: { resume: sessionId, allowedTools: [], maxTurns: 1 },
});
for await (const m of q) {
if (m.type === "assistant") {
for (const b of m.message.content) if (b.type === "text") process.stdout.write(b.text);
}
if (m.type === "result") { sessionId = m.session_id; process.stdout.write("\n"); }
}
}
rl.close();
CHATEOF
node chat.mjs
출력 예시
나> 내 이름은 우형이야 반갑습니다, 우형님. 나> 내 이름이 뭐라고 했지? 우형님이라고 하셨죠. 나> exit
핵심은 result 메시지에서 sessionId = m.session_id를 갱신해 다음 루프의 resume으로 넘기는 세 줄입니다. allowedTools: []로 도구를 전부 꺼서 순수 대화만 남겼습니다.
CHECKPOINT
에이전트에게 여러분의 코드가 가진 능력(DB 조회, 사내 API, 이 랩에서는 노트 검색)을 쥐여 줍니다. tool()로 함수를 정의하고 createSdkMcpServer()로 감싸면 별도 프로세스 없는 in-process MCP 서버가 됩니다.
Terminal, 전체 복사
cat > notes-agent.mjs << 'NOTESEOF'
// T3: 커스텀 도구, 내 함수가 에이전트의 손이 된다 (in-process MCP)
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
const NOTES = [
"7월 회식은 24일 목요일 19시, 장소는 을지로 골뱅이집",
"배포 원칙: 금요일 오후에는 프로덕션 배포를 하지 않는다",
"신규 입사자 온보딩: 버디가 2주간 매일 30분 페어링",
];
const searchNote = tool(
"search_note",
"사내 노트에서 키워드가 포함된 문장을 찾는다",
{ keyword: z.string().describe("찾을 키워드") },
async ({ keyword }) => ({
content: [{
type: "text",
text: NOTES.filter((n) => n.includes(keyword)).join("\n") || "일치하는 노트 없음",
}],
}),
);
const shelf = createSdkMcpServer({ name: "shelf", version: "1.0.0", tools: [searchNote] });
const q = query({
prompt: process.argv[2] ?? "다음 회식이 언제 어디서인지 노트에서 찾아 알려줘",
options: {
mcpServers: { shelf },
allowedTools: ["mcp__shelf__search_note"],
maxTurns: 5,
systemPrompt: "당신은 사내 노트 사서다. 반드시 search_note 도구로 찾은 내용만 근거로 답한다.",
},
});
for await (const m of q) {
if (m.type === "assistant") {
for (const b of m.message.content) if (b.type === "text") process.stdout.write(b.text);
}
if (m.type === "result") console.log(`\n--- turns=${m.num_turns}`);
}
NOTESEOF
node notes-agent.mjs
node notes-agent.mjs "금요일에 배포해도 돼?"
출력 예시
노트에 따르면 다음 회식은 7월 24일 목요일 19시, 을지로 골뱅이집입니다. --- turns=3 노트의 배포 원칙상 금요일 오후 프로덕션 배포는 하지 않습니다. --- turns=3
해부, 이름 규칙과 그라운딩
커스텀 도구의 허용 이름은 mcp__서버명__도구명 형식입니다
(mcp__shelf__search_note). zod 스키마가 입력 타입을 강제하고,
systemPrompt의 "도구로 찾은 내용만 근거로" 한 줄이 캡스톤 D에서 배운 grounding 계약의 SDK 버전입니다.
CHECKPOINT
session_id를 파일에 저장하면 프로세스가 죽어도 기억이 이어집니다. 상태 파일을 읽는 점검원을 두 번 실행해, 두 번째 실행이 **"아까는 OK였는데 지금은 DOWN"**이라고 변화를 말하게 만듭니다. 캡스톤 B 메딕의 상주화가 이 원리 위에 섭니다.
Terminal, 전체 복사
echo "service: OK" > status.txt
cat > medic-lite.mjs << 'MEDLITEEOF'
// T4: 미니 상주 점검원, 프로세스를 넘어 기억하는 에이전트
import { query } from "@anthropic-ai/claude-agent-sdk";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
const prev = existsSync(".medic-session")
? readFileSync(".medic-session", "utf8").trim()
: undefined;
const q = query({
prompt: "status.txt를 읽고 서비스 상태를 두 줄로 보고해. DOWN이면 원인 후보와 첫 조치를 제안해. 이전 점검 기록이 있으면 상태 변화를 먼저 언급해.",
options: { resume: prev, allowedTools: ["Read"], maxTurns: 4 },
});
for await (const m of q) {
if (m.type === "assistant") {
for (const b of m.message.content) if (b.type === "text") process.stdout.write(b.text);
}
if (m.type === "result") {
writeFileSync(".medic-session", m.session_id);
console.log(`\n--- 점검 완료, turns=${m.num_turns} cost=$${m.total_cost_usd}`);
}
}
MEDLITEEOF
node medic-lite.mjs
Terminal, 전체 복사
echo "service: DOWN (connection refused)" > status.txt node medic-lite.mjs
출력 예시 (2차)
직전 점검에서는 OK였는데 지금은 DOWN으로 바뀌었습니다. connection refused는 프로세스 다운 또는 포트 미개방이 유력합니다. 먼저 서비스 프로세스 상태를 확인하세요. --- 점검 완료, turns=2 cost=$0.0027
cron이나 systemd timer에 node medic-lite.mjs 한 줄을 걸면 그대로 상주 점검원입니다. 무인 실행의 안전벨트는 Chapter 5와 동일합니다: allowedTools 최소화, maxTurns 상한.
CHECKPOINT
오늘 배운 네 조각을 각자의 캡스톤에 이식하는 것이 다음 숙제입니다.
| 배운 것 | Task | 캡스톤 이식 아이디어 |
|---|---|---|
| query와 allowedTools | T1 | A 옵스 센터: /ask Lambda를 SDK 서버로 승격, 도구를 가진 질의 패널 |
| resume 대화 메모리 | T2 | C 카페: "아까 주문 취소해줘"가 되는 멀티턴 점원 |
| 커스텀 도구, grounding | T3 | D 사서: 코사인 검색을 search_shelf 도구로 감싼 대화형 사서 |
| 파일 세션, 상주 실행 | T4 | B 메딕: medic.sh를 SDK 상주 프로세스로, 점검 이력을 기억하는 메딕 |
REFERENCE
서브에이전트 정의(AgentDefinition), 훅, permissionMode, 구조화 출력까지 공식 문서가 기준입니다: code.claude.com/docs/en/agent-sdk/typescript. 포털의 레퍼런스 3부작(디렉토리 구조, 커맨드, 플러그인)도 함께 두고 쓰세요.
← Ch5. CLI Reference · Capstone Missions →
원문: Claude Code Deep Dive Workshop · Ch6. Agent SDK · 기준: 2026.07