이름이 지정된 Support Agent 만들기

CloudflareBeginner
지금 연습하기

소개

AI Agent는 흔히 추론하거나 도구를 사용할 수 있는 모델로 설명됩니다. 하지만 모델을 추가하기 전에 애플리케이션은 더 단순한 질문에 안정적으로 답할 수 있어야 합니다. 이 요청을 처리할 진행 중인 세션은 무엇인가? 지원 애플리케이션은 planning에 대한 모든 상호 작용을 같은 논리적 세션으로 보내면서 billing은 별도로 유지해야 합니다.

Cloudflare의 Agents SDK는 이 작업을 위한 상위 수준의 Agent 클래스를 제공합니다. 이름이 지정된 각 Agent는 하나의 SQLite Durable Object 인스턴스를 기반으로 합니다. SDK는 저장된 상태와 요청 라우팅을 관리하고, Durable Objects는 그 아래에서 안정적인 ID와 스토리지를 제공합니다. SDK를 마법처럼 취급하지 않고 두 계층을 모두 확인합니다.

이 실습에서는 의도적으로 LLM을 사용하지 않는 작은 지원 애플리케이션을 구축합니다.

  1. SupportAgent는 하나의 지원 세션이 저장하고 수행하는 작업을 정의합니다.
  2. SupportAgent 바인딩은 클래스 네임스페이스를 나타냅니다.
  3. /agents/support-agent/planningplanning이라는 이름의 인스턴스를 선택합니다.
  4. initialState, this.state, setState()를 사용하면 SDK가 해당 인스턴스의 작은 상태를 지속적으로 저장할 수 있습니다.

하나의 이름 지정 세션에 두 개의 메모를 작성하고, 다른 세션이 격리되어 있음을 확인합니다. 그런 다음 완전한 로컬 런타임을 중지하고 다시 시작해도 상태가 유지되는지 확인합니다. 같은 코드를 Cloudflare에 배포하고, Dashboard에서 실제 바인딩과 네임스페이스를 확인한 다음, 실습에서 만든 임시 리소스를 모두 삭제합니다.

이 과정을 시작하기 전에 LabEx를 Cloudflare 계정에 연결을 완료하세요. 이 과정에서는 LabEx VM 터미널, Wrangler 장치 인증, 계정 확인 및 계정 ID 설정을 다룹니다. 또한 O01–O06에서 간단한 TypeScript Worker와 Durable Object ID 모델을 이미 이해하고 있어야 합니다. Agents SDK, React 또는 모델 지식은 필요하지 않습니다.

현재 공식 문서에서는 SQLite 기반 Durable Objects를 Workers Free에서 사용할 수 있습니다. 이 실습에서는 삭제할 수 있는 클래스 네임스페이스 하나와 작은 Agent 인스턴스 몇 개, 제한된 요청만 생성합니다. 모델을 호출하지 않으므로 Workers Paid가 필요하지 않습니다. 설정 과정에서 /home/labex/project/named-support-agent에 Node.js 22.22.0, Agents SDK 0.23.0 및 프로젝트 로컬 Wrangler 4.134.0을 설치합니다. 로그인, 클라우드 상태 생성, 코드 배포 또는 학습자 구현 완료는 수행하지 않습니다.

VM 인증 및 Agent 구성

이 단계에서는 Wrangler를 인증하고, 사용할 학습 계정을 확인한 뒤, 아직 배포하지 않고 하나의 Agent 클래스를 설명합니다. 이 새 VM은 자체 파일 시스템을 사용하므로 Cloudflare Dashboard에 로그인되어 있어도 VM의 터미널이 인증되는 것은 아닙니다.

준비된 프로젝트 디렉터리로 이동하고 고정된 버전을 확인합니다.

cd /home/labex/project/named-support-agent
node --version
npx wrangler --version
npm list agents --depth=0

Node.js v22.22.0, Wrangler 4.134.0, agents@0.23.0이 표시되어야 합니다. 기본 Worker API보다 Agents SDK가 더 빠르게 변경되므로 버전을 고정합니다.

장치 인증 흐름을 시작합니다.

npx wrangler login --device --browser=false

Wrangler가 브라우저 URL과 짧은 장치 코드를 출력합니다. 해당 URL을 열고 코드를 입력한 다음, 선택된 계정이 전용 학습 계정인지 확인하고 인증 전에 요청된 권한을 검토합니다. Cloudflare 비밀번호나 API 토큰을 터미널에 직접 입력하지 마세요.

브라우저에 성공 메시지가 표시되면 터미널로 돌아와 Wrangler가 완료될 때까지 기다립니다. 구조화된 ID 정보를 요청합니다.

npx wrangler whoami --json

loggedIn: true인지 확인합니다. 그런 다음 계정 이름만 표시하고 LabEx Learning에 해당하는 ID를 변수에 저장합니다.

WHOAMI="$(npx wrangler whoami --json)"
printf '%s\n' "$WHOAMI" | jq '{loggedIn, authType, accounts: [.accounts[] | {name}]}'
ACCOUNT_ID="$(printf '%s\n' "$WHOAMI" | jq -r '.accounts[] | select(.name == "LabEx Learning") | .id')"
test -n "$ACCOUNT_ID"

전용 학습 계정의 표시 이름이 다르면 올바른 이름을 확인한 후에만 LabEx Learning을 해당 이름으로 바꿉니다. 계정 ID는 설정값이며 비밀 정보는 아니지만, 이 명령은 계정 ID를 불필요하게 출력하지 않습니다.

고유한 임시 Worker 이름을 만듭니다.

RUN="labex-c11-s01-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"

wrangler.jsonc를 만듭니다.

cat > wrangler.jsonc <<JSON
{
  "\$schema": "./node_modules/wrangler/config-schema.json",
  "name": "$RUN",
  "account_id": "$ACCOUNT_ID",
  "main": "src/index.ts",
  "compatibility_date": "2026-09-18",
  "compatibility_flags": ["nodejs_compat"],
  "workers_dev": true,
  "preview_urls": false,
  "observability": {
    "enabled": true,
    "head_sampling_rate": 1
  },
  "durable_objects": {
    "bindings": [
      { "name": "SupportAgent", "class_name": "SupportAgent" }
    ]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["SupportAgent"] }
  ]
}
JSON

SupportAgent 바인딩은 Worker가 클래스 네임스페이스에 접근할 때 사용하는 핸들입니다. v1 마이그레이션은 Cloudflare에 SQLite 스토리지를 사용하는 해당 클래스를 생성하도록 지시합니다. Agents는 Durable Object 기반을 사용하며, SDK가 이 리소스 계층을 없애는 것은 아닙니다. nodejs_compat는 현재 SDK에 필요합니다. 이러한 선언만으로는 배포하기 전까지 클라우드 리소스가 생성되지 않습니다.

이름이 지정된 Support Agent 구현

이 단계에서는 이름이 지정된 모든 지원 세션이 공유하는 상태와 HTTP 동작을 구현합니다. Agent 클래스는 재사용 가능한 동작이고, Agent 인스턴스planning과 같은 하나의 이름 지정 세션입니다. Cloudflare는 같은 클래스의 여러 인스턴스를 실행할 수 있으며, 각 인스턴스는 독립된 상태를 가집니다.

src/index.ts를 만듭니다.

cat > src/index.ts <<'TS'
import { Agent, routeAgentRequest } from "agents";

export interface SupportState {
  status: "new" | "active";
  noteCount: number;
  lastNote: string | null;
}

interface Env {
  SupportAgent: DurableObjectNamespace<SupportAgent>;
}

function json(value: unknown, init: ResponseInit = {}): Response {
  const headers = new Headers(init.headers);
  headers.set("content-type", "application/json; charset=utf-8");
  return new Response(JSON.stringify(value, null, 2), { ...init, headers });
}

export class SupportAgent extends Agent<Env, SupportState> {
  initialState: SupportState = {
    status: "new",
    noteCount: 0,
    lastNote: null
  };

  async onRequest(request: Request): Promise<Response> {
    if (request.method === "GET") {
      console.log(JSON.stringify({ event: "support_agent_read", instance: this.name, noteCount: this.state.noteCount }));
      return json({ instance: this.name, ...this.state });
    }

    if (request.method === "POST") {
      const body = await request.json<{ note?: unknown }>().catch(() => null);
      const note = typeof body?.note === "string" ? body.note.trim() : "";
      if (note.length < 1 || note.length > 120) {
        return json({ error: "note must contain 1-120 characters" }, { status: 400 });
      }

      this.setState({
        status: "active",
        noteCount: this.state.noteCount + 1,
        lastNote: note
      });
      console.log(JSON.stringify({ event: "support_agent_updated", instance: this.name, noteCount: this.state.noteCount }));
      return json({ instance: this.name, ...this.state });
    }

    return json({ error: "method not allowed" }, { status: 405 });
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === "/health") {
      return json({ status: "ok" });
    }

    const agentResponse = await routeAgentRequest(request, env, {
      onBeforeRequest(incoming, { name }) {
        if (!/^[a-z][a-z0-9-]{1,31}$/.test(name)) {
          return json({ error: "invalid support session name" }, { status: 400 });
        }
        return incoming;
      }
    });
    return agentResponse ?? json({ error: "not found" }, { status: 404 });
  }
} satisfies ExportedHandler<Env>;
TS

안쪽 코드부터 중요한 부분을 확인합니다.

  • initialState는 새로 생성된 이름 지정 인스턴스가 처음 사용하는 값입니다.
  • this.state는 해당 인스턴스의 현재 SDK 관리 상태를 읽습니다.
  • setState()는 대체 상태를 동기적으로 검증하고 인스턴스의 SQLite 스토리지에 저장합니다. 이후 실습에서는 연결된 클라이언트와 상태를 동기화하는 기능도 다룹니다.
  • this.name은 라우팅으로 선택된 안정적인 인스턴스 이름입니다. 클래스 이름이나 무작위 프로세스 ID가 아닙니다.
  • routeAgentRequest()/agents/<binding>/<name>을 올바른 Agent로 연결합니다. URL에서는 SupportAgent 바인딩이 support-agent가 됩니다.
  • onBeforeRequest는 Durable Object 인스턴스를 선택하기 전에 잘못된 이름을 거부하므로 불필요한 영속 ID가 생성되지 않습니다.

로그에는 인스턴스 이름과 카운트라는 합성 정보만 기록합니다. 이후 Dashboard 실습에서 지원 내용이 저장되지 않도록 메모 내용은 의도적으로 제외합니다.

실행 전에 타입 생성 및 빌드

이 단계에서는 구성 정보를 반영한 타입을 생성하고 Worker를 배포하지 않은 상태로 빌드합니다. 생성된 Worker 타입은 구성을 TypeScript에 연결하므로, 로컬 프로세스나 클라우드 배포에 시간을 사용하기 전에 잘못 입력한 바인딩이나 클래스를 발견할 수 있습니다.

wrangler.jsonc에서 타입을 생성합니다.

npx wrangler types

Wrangler가 worker-configuration.d.ts를 작성합니다. 관련 없는 생성 내용을 출력하지 않고 구성된 Agent 바인딩이 포함되어 있는지 확인합니다.

grep -n "SupportAgent" worker-configuration.d.ts | head

TypeScript 컴파일러를 실행합니다.

npm run check

스크립트 헤더 이후에 출력이 없으면 컴파일러가 오류를 찾지 못한 것입니다. 이제 Wrangler에 Cloudflare에 연결하거나 리소스를 생성하지 않고 배포 번들을 빌드하도록 요청합니다.

npx wrangler deploy --dry-run --outdir .labex/dry-run

업로드 크기 요약과 SupportAgent Durable Object 바인딩이 성공적으로 표시되어야 합니다. dry run은 번들링과 구성을 로컬에서 검증할 뿐이며, 인증, 원격 스토리지 또는 엣지 동작을 검증하지는 않습니다.

로컬 ID와 재시작 후 지속성 검증

이 단계에서는 세 가지 속성을 확인합니다. 같은 이름을 반복해서 사용하면 같은 상태에 도달하고, 다른 이름은 격리된 상태를 유지하며, 개발 프로세스를 완전히 다시 시작해도 저장된 상태가 유지됩니다.

로컬 Workers 런타임을 백그라운드에서 시작합니다.

npm run dev > .labex/dev.log 2>&1 &
echo $! > .labex/dev.pid
for attempt in $(seq 1 30); do
  if curl --silent --fail http://127.0.0.1:8787/health; then
    break
  fi
  sleep 1
done

{"status":"ok"}가 표시되어야 합니다. 새 planning Agent를 읽습니다.

curl --silent http://127.0.0.1:8787/agents/support-agent/planning | jq

처음에는 status: "new", noteCount: 0, lastNote: null이 표시됩니다. 합성 메모 두 개를 추가합니다.

curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Customer cannot open the invoice"}' \
  http://127.0.0.1:8787/agents/support-agent/planning | jq
curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Asked customer to retry"}' \
  http://127.0.0.1:8787/agents/support-agent/planning | jq

두 번째 응답에는 instance: "planning", status: "active", noteCount: 2와 두 번째 메모가 표시됩니다. 두 요청이 같은 URL 이름을 사용했으므로 같은 논리적 Agent에 도달했습니다.

다른 인스턴스를 읽습니다.

curl --silent http://127.0.0.1:8787/agents/support-agent/support | jq

support는 카운트 0인 자체 초기 상태를 유지합니다. 두 이름은 클래스 동작은 공유하지만 저장된 값은 공유하지 않습니다.

잘못된 이름을 거부하는지 확인합니다.

curl --silent --write-out '\nHTTP %{http_code}\n' \
  http://127.0.0.1:8787/agents/support-agent/INVALID

invalid support session name과 HTTP 400이 표시되어야 합니다.

방금 시작한 정확한 프로세스를 중지한 다음, 같은 로컬 지속성 디렉터리를 사용하는 새 프로세스를 실행합니다.

kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
npm run dev > .labex/dev-restart.log 2>&1 &
echo $! > .labex/dev.pid
for attempt in $(seq 1 30); do
  if curl --silent --fail http://127.0.0.1:8787/health; then
    break
  fi
  sleep 1
done
curl --silent http://127.0.0.1:8787/agents/support-agent/planning | jq
curl --silent http://127.0.0.1:8787/agents/support-agent/support | jq

Wrangler를 완전히 다시 시작한 후에도 planning2, support0으로 유지됩니다. 이는 하나의 JavaScript 프로세스에서 두 번 읽은 것보다 강력한 증거입니다. 데이터가 로컬 Durable Object 지속성 디렉터리에서 다시 로드되었기 때문입니다.

Cloud Agent 인스턴스 배포 및 실행

이 단계에서는 변경하지 않은 애플리케이션을 배포하고 실제 클라우드가 소유한 Agent 인스턴스를 실행합니다. 로컬에서 얻은 결과만으로는 선택한 Cloudflare 계정이 리소스를 소유하는지 또는 엣지 런타임이 같은 이름 지정 ID를 제공하는지 증명할 수 없습니다.

로컬 프로세스를 중지하고 배포합니다.

kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
npx wrangler deploy

Wrangler가 v1 마이그레이션을 적용하고, SQLite 기반 SupportAgent 클래스 네임스페이스를 생성한 뒤, 공개 workers.dev URL을 출력합니다. 예시 URL을 실제 URL로 바꾸어 저장합니다.

WORKER_URL="https://YOUR_WORKER_URL"

상태를 저장하지 않는 health 라우트가 응답할 때까지 기다립니다.

for attempt in $(seq 1 30); do
  if curl --silent --fail "$WORKER_URL/health"; then
    break
  fi
  sleep 2
done

이제 합성 데이터를 사용해 클라우드가 소유한 인스턴스를 실행합니다.

curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Cloud planning note one"}' \
  "$WORKER_URL/agents/support-agent/planning" | jq
curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Cloud planning note two"}' \
  "$WORKER_URL/agents/support-agent/planning" | jq
curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Independent support note"}' \
  "$WORKER_URL/agents/support-agent/support" | jq

두 인스턴스를 읽습니다.

curl --silent "$WORKER_URL/agents/support-agent/planning" | jq
curl --silent "$WORKER_URL/agents/support-agent/support" | jq

클라우드의 planning Agent 카운트는 2, 독립된 support Agent 카운트는 1이어야 합니다. 로컬 스토리지와 클라우드 스토리지는 의도적으로 분리되어 있지만, 두 환경 모두 같은 이름-인스턴스 연결 규칙을 구현합니다.

검증기는 실행마다 고유한 Agent 이름 두 개를 생성하고 초기 상태, 같은 이름의 지속성, 다른 이름의 격리 및 잘못된 이름 거부를 반복해서 확인합니다. 로컬 파일이나 명령 기록을 원격 동작의 증거로 취급하지 않습니다.

런타임 결과를 Dashboard에 연결

이 단계에서는 터미널에서 확인한 동작을 Dashboard에 표시되는 바인딩, 네임스페이스 및 로그와 연결합니다. 스크린샷의 이름, 타임스탬프 및 총계는 테스트 실행의 예시입니다. 자신의 터미널에서 생성한 고유한 labex-c11-s01-... 이름을 사용하세요.

Cloudflare Dashboard에서 Workers & Pages를 열고 임시 Worker를 선택합니다. 개요 화면에서 배포된 애플리케이션과 최근 트래픽을 확인할 수 있습니다.

Workers and Pages에 배포된 이름 지정 Support Agent Worker

Worker의 Bindings 탭을 엽니다. SupportAgentSupportAgent Durable Object 클래스에 연결되어 있는지 확인합니다. 첫 번째 레이블은 Worker 코드와 라우팅에서 사용하는 이름이고, 클래스 이름은 src/index.ts에서 내보낸 구현을 가리킵니다.

Durable Object 클래스에 연결된 SupportAgent 바인딩

Developer Platform 탐색 메뉴에서 Durable Objects를 열고 정확한 Worker가 소유한 네임스페이스를 선택합니다. 클래스가 SupportAgent이고 Storage: SQL인지 확인합니다. 네임스페이스는 클래스 수준의 모음이며, planning, support 및 검증기 이름은 그 안에 있는 개별 인스턴스입니다. 예시 이미지에서는 개인정보 보호를 위해 실행별 네임스페이스 ID를 표시하지 않습니다.

SQL 스토리지를 표시하는 SupportAgent 네임스페이스

Worker로 돌아가 Observability → Logs를 엽니다. support_agent_read 또는 support_agent_updated 애플리케이션 이벤트를 찾아 펼칩니다. 이벤트의 합성 instancenoteCount를 제한된 요청과 대조합니다. 애플리케이션은 메모 내용을 로그에 기록하지 않습니다.

인스턴스 이름과 메모 카운트를 표시하는 구조화된 Support Agent 이벤트

Dashboard의 메트릭과 로그는 늦게 도착할 수 있으므로 최근 차트가 비어 있어도 결과를 단정할 수 없습니다. 인증된 API, 네임스페이스 소유권 및 실시간 런타임 검사가 여전히 권위 있는 확인 방법입니다. 스크린샷은 동일한 관계가 화면에서 어떻게 표시되는지 알려 줄 뿐이며, 학습자 제출물이 아닙니다.

Agent 네임스페이스와 Worker 삭제

이 단계에서는 VM이 아직 인증된 상태에서 정확한 Agent 네임스페이스와 Worker를 영구적으로 삭제합니다. Agent 상태는 Durable Object 클래스 네임스페이스에 속하므로 Worker 스크립트만 삭제하는 것은 저장된 상태를 지우라는 명시적 요청이 아닙니다. Cloudflare 마이그레이션은 추가 전용입니다. v1을 유지한 채 정확한 클래스에 대한 삭제 마이그레이션 v2를 추가해야 합니다.

Agent를 내보내지 않는 작은 정리용 진입점을 만듭니다.

cat > src/cleanup.ts <<'TS'
export default {
  fetch() {
    return Response.json({ status: "cleanup" }, { status: 410 });
  }
};
TS

원래 구성에서 정확한 이름과 계정을 읽은 다음 wrangler.cleanup.jsonc를 만듭니다.

RUN="$(node -e 'console.log(JSON.parse(require("fs").readFileSync("wrangler.jsonc", "utf8")).name)')"
ACCOUNT_ID="$(node -e 'console.log(JSON.parse(require("fs").readFileSync("wrangler.jsonc", "utf8")).account_id)')"
cat > wrangler.cleanup.jsonc <<JSON
{
  "\$schema": "./node_modules/wrangler/config-schema.json",
  "name": "$RUN",
  "account_id": "$ACCOUNT_ID",
  "main": "src/cleanup.ts",
  "compatibility_date": "2026-09-18",
  "compatibility_flags": ["nodejs_compat"],
  "workers_dev": true,
  "preview_urls": false,
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["SupportAgent"] },
    { "tag": "v2", "deleted_classes": ["SupportAgent"] }
  ]
}
JSON

v1을 유지하는 것이 중요합니다. 마이그레이션 기록은 다시 작성할 설명이 아니라 순서가 있는 이력입니다. v2는 해당 클래스 네임스페이스와 그 안의 모든 임시 이름 지정 인스턴스를 영구적으로 삭제합니다.

삭제 마이그레이션을 배포합니다.

npx wrangler deploy --config wrangler.cleanup.jsonc

마이그레이션 출력을 읽고 고유한 Worker에 속한 SupportAgent만 삭제되었는지 확인합니다. 그런 다음 상태를 저장하지 않는 정리용 Worker를 삭제합니다.

npx wrangler delete --config wrangler.cleanup.jsonc --force

확인 메시지가 표시되면 정확한 labex-c11-s01-... 애플리케이션을 확인합니다. Workers & Pages에서 해당 Worker가 사라졌는지 확인합니다. 이 테스트 계정에는 관련 없는 애플리케이션이 없으므로 성공한 실행에서는 전체 목록이 비어 있습니다. 다른 프로젝트가 있는 계정에서는 관련 없는 행을 그대로 유지해야 합니다.

임시 Worker 삭제 후 프로젝트가 없는 Workers and Pages

Durable Objects를 열고 삭제한 Worker가 소유하던 네임스페이스도 사라졌는지 확인합니다. 승인된 테스트 계정에는 관련 없는 네임스페이스가 없으므로 Durable Objects 목록이 비어 있습니다. 이 예시와 맞추기 위해 다른 프로젝트의 네임스페이스를 삭제하지 마세요.

SupportAgent 클래스 삭제 후 네임스페이스가 없는 Durable Objects

과거 로그는 잠시 남아 있을 수 있으며 활성 리소스가 아닙니다.

로그아웃하기 전에 인증된 리소스 부재 확인을 실행합니다.

python3 .labex/verify.py deleted

PASS: deleted만 표시되어야 선택한 계정에 소유한 두 리소스가 모두 더 이상 없다는 것을 증명할 수 있습니다. 인증이 사라져 발생한 404나 네트워크 오류는 삭제 증거로 인정되지 않습니다.

이 VM의 인증 취소

이 단계에서는 이 임시 VM에 저장된 OAuth 인증을 삭제합니다. 클라우드 리소스 정리와 로컬 자격 증명 정리는 서로 다른 문제입니다. Worker와 네임스페이스는 이미 삭제했습니다.

로그아웃합니다.

npx wrangler logout

Wrangler에 구조화된 상태를 요청합니다.

npx wrangler whoami --json

결과에 "loggedIn": false가 명시적으로 포함되어야 합니다. 테스트한 Wrangler 버전은 여러 인증 상태에서 일반 출력이 나타날 수 있으므로, 이 구조화된 값이 단순한 안내 메시지보다 확실한 확인 방법입니다. 네트워크 오류가 발생하면 로그아웃으로 해석하지 말고 다시 시도해야 합니다.

이 실습에서 생성한 두 종류의 상태를 모두 삭제했습니다. 원격 Support Agent 네임스페이스와 Worker, 그리고 VM의 로컬 인증 정보입니다.

요약

AI 용어 뒤에 기반 기술을 숨기지 않고 이 과정의 첫 번째 Cloudflare Agent를 구축했습니다. 하나의 Agent 클래스가 동작을 정의하고, 바인딩이 SQLite Durable Object 네임스페이스를 노출하며, 안정적인 URL 이름이 하나의 논리적 인스턴스를 선택한다는 것을 배웠습니다. 또한 Agents SDK가 this.statesetState()를 통해 initialState의 변경 사항을 지속적으로 저장한다는 것도 확인했습니다.

로컬에서 같은 이름의 지속성, 다른 이름의 격리 및 프로세스 재시작 후 지속성을 검증했습니다. Cloudflare 학습 계정에서도 같은 규칙을 반복해서 확인하고, 런타임 결과를 Dashboard의 바인딩, 네임스페이스 및 개인정보를 제한한 로그와 연결했습니다. 마지막으로 클라우드 리소스와 VM 인증 정보를 모두 삭제했습니다. 다음 실습에서는 브라우저 클라이언트를 이 상태에 연결하고 제어된 실시간 동기화를 추가합니다.