Alejandro Rioja.
AI Agents

30개 이상의 프로덕션 에이전트를 운영하는 내 에이전트 스택 (Python 없이)

Alejandro Rioja
Alejandro Rioja
4 분 읽기
TL;DR

TypeScript, Cloudflare Workers/Queues/KV, Claude 모델을 사용해 30개 이상의 프로덕션 AI 에이전트를 운영한다. Python도 에이전트 프레임워크도 없다. 스택은 의도적으로 단순하다: Workers가 스케줄링과 큐잉을 담당하고, KV가 상태를 저장하며, Anthropic SDK가 모델 호출을 직접 처리한다. 중요한 제약은 AI 레이어가 아니라 그 주변의 인프라다.

무료 뉴스레터

매주 수요일. 28,400명+ 구독자. 핵심만.

목차

2026년 5월 업데이트.

TL;DR: TypeScript, Cloudflare Workers/Queues/KV, Claude 모델을 사용해 30개 이상의 프로덕션 AI 에이전트를 운영한다. Python도 에이전트 프레임워크도 없다. 스택은 의도적으로 단순하다: Workers가 스케줄링과 큐잉을 담당하고, KV가 상태를 저장하며, Anthropic SDK가 모델 호출을 직접 처리한다. 중요한 제약은 AI 레이어가 아니라 그 주변의 인프라다.

[운영자 관점] 나는 두 개의 비즈니스를 운영한다: AI 컨설팅 브랜드와 텍사스 주 플루거빌에 있는 피클볼 시설 Pickleland다. 둘을 합쳐 현재 프로덕션에서 30개 이상의 에이전트가 실행 중이다. 이것은 데모가 아니라 실제 스택이다.

왜 Python을 쓰지 않는가

솔직한 답: 웹사이트와 제품 작업을 위해 매일 TypeScript를 쓴다. 에이전트를 위해 두 번째 언어를 추가하면 두 개의 런타임, 두 개의 의존성 트리, 두 개의 배포 파이프라인이 생긴다. 생산성 비용은 이론적인 게 아니다 — 이전 프로젝트에서 실제로 치렀고, 다시 하지 않기로 결심했다.

두 번째 이유는 Cloudflare다. Workers는 엣지에서 TypeScript를 네이티브로 실행하며, Queues, KV, Durable Objects, Cron Triggers가 내장되어 있다. 스케줄링, 상태, 비동기 작업 처리 등 필요한 모든 에이전트 인프라가 wrangler deploy 하나면 된다. 같은 수준의 운영 단순성을 가진 Python 동등물은 존재하지 않는다.

세 번째 이유: 대부분의 “Python이 AI에 더 좋다”는 주장은 실제로 “Python에 ML 라이브러리가 더 많다”는 뜻이다. 나는 모델을 훈련시키지 않는다. API를 호출한다. Anthropic SDK는 일급 TypeScript다. LangChain과 유사 도구들은 원하지 않는 복잡성이다. 에이전트를 연구하는 게 아니라 배포할 때는 단순함이 이긴다.

핵심 인프라: 세 가지 Cloudflare 프리미티브

내가 운영하는 모든 에이전트는 이 세 가지 중 적어도 하나에 닿는다:

Cloudflare Workers — 컴퓨팅 레이어. Worker는 에이전트의 런타임이다: 트리거(cron, Queue 메시지, HTTP)를 받아 모델 호출을 실행하고 어딘가에 출력을 쓴다. 콜드 스타트 5ms 미만. 무료 플랜에서 CPU 시간 30초, 유료 플랜에서 15분 실행 제한. 내가 만드는 거의 모든 것이 30초 안에 들어간다. 안 들어가는 것들은 팬아웃을 위해 Queues를 사용한다.

Cloudflare Queues — 비동기 작업 처리. 작업이 요청보다 오래 걸릴 수 있을 때, 또는 팬아웃이 필요할 때(12개 번역을 병렬로 생성), Queue에 메시지를 푸시하고 바인딩된 컨슈머들이 독립적으로 처리하게 한다. 폴링 없음, setTimeout 해킹 없음.

Cloudflare KV — 경량 상태 저장. 에이전트 실행 기록, 마지막으로 처리된 타임스탬프, 캐시된 API 응답. KV는 최종 일관성이지만 에이전트에는 충분하다 — 트랜잭션을 실행하지 않는다. 데이터베이스를 스핀업하지 않고도 어떤 Worker에서나 읽고 쓸 수 있는 단순한 키-값 저장소를 제공한다.

모델 레이어: Anthropic SDK, 두 가지 모델

정확히 두 가지 Claude 모델을 사용한다:

TypeScript의 Anthropic SDK는 직관적이다. 모든 모델 호출에 사용하는 패턴:

typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY });

async function runAgent(prompt: string, systemPrompt: string): Promise<string> {
  const message = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 2048,
    system: systemPrompt,
    messages: [{ role: "user", content: prompt }],
  });

  const block = message.content[0];
  if (block.type !== "text") throw new Error("Unexpected content type");
  return block.text;
}

이게 전체 모델 인터페이스다. 그 위에 추상화 없음. 툴 사용이 필요하면 tools 배열을 추가한다. 스트리밍이 필요하면 messages.createmessages.stream으로 교체한다. 프레임워크가 이걸 나 대신 관리하지 않는다 — 그걸 원하지도 않는다.

실제 에이전트: 콘텐츠 파이프라인

내가 운영하는 가장 복잡한 에이전트는 콘텐츠 파이프라인이다. 블로그 포스트를 생성하고, 12개 언어로 번역하고, OG 카드 SVG를 렌더링하고, LinkedIn 프로모션을 초안화한다 — 모두 초안으로, 내 검토 없이는 아무것도 게시되지 않는다.

Worker 진입점:

typescript
// src/workers/content-pipeline.ts
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { topic, slug } = await request.json<{ topic: string; slug: string }>();

    // Step 1: EN 포스트 생성
    const enPost = await generatePost(topic, env);
    await env.CONTENT_KV.put(`draft:${slug}:en`, enPost);

    // Step 2: Queue를 통해 번역 팬아웃
    const locales = ["ar", "de", "es", "fr", "hi", "it", "ja", "ko", "nl", "pt", "ru", "zh"];
    for (const locale of locales) {
      await env.TRANSLATION_QUEUE.send({ slug, locale, content: enPost });
    }

    return Response.json({ status: "queued", slug });
  },
};

각 번역은 자체 Worker 호출로 실행된다. 하나가 실패하면 Queue가 자동으로 재시도한다. 스레드, 프로미스, 레이트 리밋 백오프를 직접 관리하지 않고 12개의 병렬 번역을 얻는다.

실제 에이전트: 이벤트 프로모터

Pickleland는 피클볼 이벤트를 운영한다. 다음 4일간의 이벤트를 예약 플랫폼에서 스캔하고, 이벤트별로 Facebook 그룹 포스트를 초안화하고, 아무것도 나가기 전에 내 검토를 위해 제출하는 에이전트를 만들었다.

시스템 프롬프트:

typescript
const systemPrompt = `You are a community manager for a pickleball facility.
Write Facebook group posts for upcoming events.
Rules:
- Max 150 words per post
- Lead with what's fun about the event, not the price
- Include the booking URL exactly as provided
- Do not use exclamation marks more than once per post
- Tone: friendly, local, not corporate`;

여기서 중요한 제약은 모델이 아니라 워크플로우다. 에이전트는 매일 오전 8시 cron 트리거로 실행된다. 초안 포스트는 검토 큐에 들어간다. 승인하거나 편집하면 별도의 게시 Worker가 실행된다. 사람이 보지 않은 이벤트는 게시되지 않는다.

30개 이상의 에이전트를 어떻게 관리하는가

Cloudflare 대시보드가 내 컨트롤 플레인이다. 각 Worker는 호출 횟수, 오류율, CPU 시간을 보여준다. 각 Queue는 메시지 처리량과 실패를 보여준다. KV는 스토리지 사용량을 보여준다.

그 외에:

규율은 기술적인 게 아니다. 에이전트가 자율적으로 할 수 있는 것과 내 승인이 필요한 것을 결정하는 것이다. 콘텐츠 초안: 자율적. 고객을 건드리는 것: 사람 검토. 돈을 움직이는 것: 에이전트 일이 아님.

오늘 다시 시작한다면 바꿀 것

하나: 처음부터 구조화된 출력(JSON 모드)을 설정할 것이다. 이미 출시된 에이전트에 나중에 적용하는 게 아니라. Claude의 자유형 텍스트를 파싱하는 것은 세금이다. Zod 스키마를 정의하고 예상 응답 형식으로 전달하면 타입이 지정된 데이터를 받고 다운스트림 Workers가 추측할 필요가 없다.

typescript
import { z } from "zod";

const EventPostSchema = z.object({
  headline: z.string().max(80),
  body: z.string().max(600),
  bookingUrl: z.string().url(),
  suggestedPostTime: z.enum(["morning", "afternoon", "evening"]),
});

운영자의 결론

프로덕션에서 작동하는 에이전트 스택은 뭔가 망가졌을 때 밤 10시에 디버그할 수 있는 것이다. 나에게는 TypeScript + Cloudflare + Anthropic SDK다 — 가장 화려한 조합이어서가 아니라, 각 레이어가 독립적으로 관찰 가능하고, 배포 가능하며, 교체 가능하기 때문이다. 프레임워크는 추상화에 대한 베팅이다. 나는 직접 배관을 소유하는 것을 선호한다.

계속 읽기

AI 플레이북을 받아보세요

매주 수요일. 28,400명+ 구독자. 핵심만.

↵ 전체 결과 보기 esc esc 닫기