⌂ 목차 ← 이전 다음 →원본 페이지

Ch6. Agent SDK

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

TASK 00 · 사전 준비, SDK 설치와 인증 상속 (10분)

Agent SDK는 Claude Code CLI를 자식 프로세스로 구동합니다. 그래서 설치만 하면 Chapter 1의 인증과 Chapter 3의 Bedrock 설정을 그대로 상속합니다. 새 자격증명이 필요 없습니다.

Step 1 · 작업 폴더와 설치

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=1ANTHROPIC_MODEL=global.anthropic.claude-sonnet-4-6.

격리 기본값

SDK는 기본적으로 파일시스템 설정(settingSources)을 로드하지 않습니다. CLAUDE.md나 프로젝트 설정 없이 깨끗한 상태로 시작하는 것이 기본이고, 필요할 때만 settingSources: ["user", "project"]처럼 명시해 불러옵니다. 임베딩된 에이전트가 예측 가능해야 하기 때문입니다.

CHECKPOINT

TASK 01 · 첫 query, CLI의 프로그래매틱 쌍둥이 (12분)

claude -p로 하던 일을 query() 함수로 합니다. 스트리밍 메시지를 순회하며 Chapter 5에서 본 그 JSON 필드들(session_id, num_turns, total_cost_usd)이 타입 있는 객체로 돌아오는 것을 확인합니다.

Step 1 · hello.mjs 배치와 실행

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

TASK 02 · 대화 메모리, resume이 기억을 만든다 (12분)

query는 기본적으로 매번 새 대화입니다. 첫 응답의 session_id를 붙잡아 다음 query의 resume에 넘기면 대화가 이어집니다. Chapter 5의 --resume과 같은 원리를 코드 3줄로 구현합니다.

Step 1 · chat.mjs 배치와 대화 시험

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

TASK 03 · 커스텀 도구, 내 함수가 에이전트의 손이 된다 (14분)

에이전트에게 여러분의 코드가 가진 능력(DB 조회, 사내 API, 이 랩에서는 노트 검색)을 쥐여 줍니다. tool()로 함수를 정의하고 createSdkMcpServer()로 감싸면 별도 프로세스 없는 in-process MCP 서버가 됩니다.

Step 1 · notes-agent.mjs 배치와 질의

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

TASK 04 · 미니 상주 점검원, 프로세스를 넘는 기억 (12분)

session_id를 파일에 저장하면 프로세스가 죽어도 기억이 이어집니다. 상태 파일을 읽는 점검원을 두 번 실행해, 두 번째 실행이 **"아까는 OK였는데 지금은 DOWN"**이라고 변화를 말하게 만듭니다. 캡스톤 B 메딕의 상주화가 이 원리 위에 섭니다.

Step 1 · 점검원 배치와 1차 점검

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

Step 2 · 장애 주입과 2차 점검

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와 allowedToolsT1A 옵스 센터: /ask Lambda를 SDK 서버로 승격, 도구를 가진 질의 패널
resume 대화 메모리T2C 카페: "아까 주문 취소해줘"가 되는 멀티턴 점원
커스텀 도구, groundingT3D 사서: 코사인 검색을 search_shelf 도구로 감싼 대화형 사서
파일 세션, 상주 실행T4B 메딕: 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