Agent SDK, Claude Code를 코드에서 부른다
지금까지는 터미널에서 Claude Code와 대화했습니다. 이 랩에서는 같은 에이전트 루프를 여러분의 프로그램 안으로 가져옵니다. query 한 번으로 도구를 쓰는 에이전트를 호출하고, session을 이어 대화 기억을 만들고, 내 함수를 커스텀 도구로 쥐여 주고, 프로세스가 죽어도 기억을 이어 가는 미니 상주 점검원까지 60분에 완주합니다.
이 챕터의 진행 흐름
개요
SDK 설치에서 상주 에이전트까지, 다섯 Task가 한 단계씩 코드 속의 Claude를 완성합니다.
진행 중 위치가 헷갈리면 이 흐름도로 돌아오세요.
상세 흐름도 펼쳐 보기
사전 준비, SDK 설치와 인증 상속
10분
Agent SDK는 Claude Code CLI를 자식 프로세스로 구동합니다.
그래서 설치만 하면
Chapter 1의 인증과 Chapter 3의 Bedrock 설정을 그대로 상속합니다.
새 자격증명이 필요 없습니다.
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.
settingSources: ["user", "project"]처럼 명시해 불러옵니다.
임베딩된 에이전트가 예측 가능해야 하기 때문입니다.
첫 query, CLI의 프로그래매틱 쌍둥이
12분
claude -p로 하던 일을 query() 함수로 합니다.
스트리밍 메시지를 순회하며 Chapter 5에서 본 그 JSON 필드들(session_id, num_turns, total_cost_usd)이
타입 있는 객체로 돌아오는 것을 확인합니다.
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와
같은 개념이 옵션 객체 한 줄이 된 것뿐입니다.
m.message.content 안에 블록 배열로 들어 있습니다.
text 블록만 골라 출력하는 위 패턴을 그대로 쓰세요. 최종 텍스트만 필요하면
result 메시지의 m.result가 가장 간단합니다.
대화 메모리, resume이 기억을 만든다
12분
query는 기본적으로 매번 새 대화입니다.
첫 응답의 session_id를 붙잡아 다음 query의
resume에 넘기면 대화가 이어집니다.
Chapter 5의 --resume과 같은 원리를
코드 3줄로 구현합니다.
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: []로 도구를 전부 꺼서 순수 대화만 남겼습니다.
커스텀 도구, 내 함수가 에이전트의 손이 된다
14분
에이전트에게 여러분의 코드가 가진 능력(DB 조회, 사내 API, 이 랩에서는 노트 검색)을 쥐여 줍니다.
tool()로 함수를 정의하고 createSdkMcpServer()로 감싸면
별도 프로세스 없는 in-process MCP 서버가 됩니다.
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 버전입니다.
미니 상주 점검원, 프로세스를 넘는 기억
12분
session_id를 파일에 저장하면 프로세스가 죽어도 기억이 이어집니다.
상태 파일을 읽는 점검원을 두 번 실행해, 두 번째 실행이 "아까는 OK였는데 지금은 DOWN"이라고
변화를 말하게 만듭니다.
캡스톤 B 메딕의 상주화가 이 원리 위에 섭니다.
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
echo "service: DOWN (connection refused)" > status.txt
node medic-lite.mjs
직전 점검에서는 OK였는데 지금은 DOWN으로 바뀌었습니다.
connection refused는 프로세스 다운 또는 포트 미개방이 유력합니다. 먼저 서비스 프로세스 상태를 확인하세요.
--- 점검 완료, turns=2 cost=$0.0027
cron이나 systemd timer에 node medic-lite.mjs 한 줄을 걸면
그대로 상주 점검원입니다. 무인 실행의 안전벨트는 Chapter 5와 동일합니다:
allowedTools 최소화, maxTurns 상한.
같은 머신의 세션끼리 메시지를 주고받는 SendMessage / ListAgents가 Bedrock 환경에서도 동작하게 되었습니다. 상주 점검원이 발견한 이슈를 지금 열려 있는 개발 세션으로 직접 알리는 그림이 가능해진 것입니다. 터미널을 하나 더 열어 확인해 보세요.
cd ~/claude-lab/ch1 && claude
지금 이 머신에서 실행 중인 다른 Claude 세션 목록을 보여주고, 첫 번째 세션에 "점검원 테스트 메시지"를 보내 주세요
원래 세션으로 돌아가면 보낸 메시지가 한 줄 미리보기로 도착해 있습니다(Ctrl+O로 전체 펼침, v2.1.247+). T4의 상주 점검원 스크립트가 보고서를 파일 대신 살아 있는 세션으로 배달하도록 확장하는 것이 다음 실험 과제입니다.
마무리, 캡스톤 이식 지도
오늘 배운 네 조각을 각자의 캡스톤에 이식하는 것이 다음 숙제입니다.
| 배운 것 | Task | 캡스톤 이식 아이디어 |
|---|---|---|
| query와 allowedTools | T1 | A 옵스 센터: /ask Lambda를 SDK 서버로 승격, 도구를 가진 질의 패널 |
| resume 대화 메모리 | T2 | C 카페: "아까 주문 취소해줘"가 되는 멀티턴 점원 |
| 커스텀 도구, grounding | T3 | D 사서: 코사인 검색을 search_shelf 도구로 감싼 대화형 사서 |
| 파일 세션, 상주 실행 | T4 | B 메딕: medic.sh를 SDK 상주 프로세스로, 점검 이력을 기억하는 메딕 |
READING CHECK
이 페이지를 읽으셨나요?
읽음 표시는 실습 체크포인트와 별도로 관리됩니다. 아직 읽지 않음