HANDS-ON LAB / CHAPTER 6

Agent SDK, Claude Code를 코드에서 부른다

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 설정을 그대로 상속합니다. 새 자격증명이 필요 없습니다.

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)이 타입 있는 객체로 돌아오는 것을 확인합니다.

1hello.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줄로 구현합니다.

1chat.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 서버가 됩니다.

1notes-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 메딕의 상주화가 이 원리 위에 섭니다.

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
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부작(디렉토리 구조, 커맨드, 플러그인)도 함께 두고 쓰세요.