왜 AI 에이전트는 앱 안에서 다른 경로가 필요한가?
애플리케이션으로 들어오는 요청 중 점점 더 많은 비중이 AI 에이전트에서 발생합니다. 예를 들어 사용자를 대신해 문서를 가져오는 assistant, 제품 페이지를 읽는 research tool, API를 호출하는 autonomous workflow가 있습니다. 이들 대부분은 사람을 위해 설계된 인터페이스를 통해 동작합니다. 전체 HTML 페이지를 내려받고, 레이아웃을 파싱하고, 마우스를 전제로 한 탐색을 따라가며, 스크립트가 많은 페이지가 예상대로 렌더링되지 않으면 재시도합니다.
그 방식은 모두에게 비효율적입니다. 에이전트는 불필요한 콘텐츠를 받아 오류를 내고, 서버는 아무도 보지 않는 페이지를 렌더링하느라 자원을 씁니다. 또 트래픽은 이상하게 동작하는 브라우저처럼 보여서 해석하기 어렵습니다. 아직 이 트래픽의 비중이 어느 정도인지 확신이 없다면, 웹사이트 트래픽이 AI 에이전트에서 오는지 확인하는 방법부터 보세요.
더 나은 방법은 에이전트에게 구조화된 경로를 주는 것입니다. 프로그램용으로 만든 형식의 콘텐츠와 작업을 표준 위치에서 발견할 수 있게 제공하고, 자신을 올바르게 식별하는 클라이언트에 더 나은 제한을 적용하는 방식입니다. 이 가이드는 두 번째 애플리케이션을 만들지 않고도 이를 구현하는 방법을 보여줍니다.
1단계: 에이전트가 무엇을 할 수 있어야 하는지 결정하기
기술보다 의도를 먼저 보세요. 이미 에이전트 트래픽이 보이는 route를 살펴보고 다음처럼 묶습니다.
- 콘텐츠 읽기: 문서, 제품 정보, 가격 페이지, 도움말 글, 공개 프로필.
- 데이터 조회: 검색, 카탈로그 조회, 가용성, 상태.
- 작업 수행: 사용자를 대신해 무언가를 생성, 수정 또는 삭제.
읽기와 조회는 잘 동작하는 어떤 클라이언트에도 열려 있는 구조화된 경로의 좋은 후보입니다. 작업 수행은 클라이언트가 자신을 어떻게 식별하든 상관없이 인증과 권한 부여가 필요하며, 민감한 작업을 보호하는 가이드에서 다룹니다.
2단계: 기계 친화적인 형식 제공하기
콘텐츠 route에는 페이지의 외형 없이 같은 정보를 제공하세요. 대부분의 요구는 다음 두 형식으로 충족됩니다.
- Markdown은 문서와 글 같은 산문형 콘텐츠에 적합하며, RFC 7763에 등록된
text/markdownmedia type으로 제공합니다. 제목, 목록, 링크, 코드 블록을 유지하면서도 간결합니다. - JSON은 제품, 요금제, 상태 같은 구조화된 record에 적합합니다.
content negotiation을 사용하면 URL은 그대로 두고 클라이언트가 원하는 형식을 요청할 수 있습니다.
import express from "express";
const app = express();
app.get("/docs/:slug", async (req, res) => {
const doc = await loadDoc(req.params.slug);
if (!doc) return res.status(404).end();
res.vary("Accept");
const preferred = req.accepts(["text/html", "text/markdown", "application/json"]);
if (preferred === "text/markdown") {
return res.type("text/markdown; charset=utf-8").send(doc.markdown);
}
if (preferred === "application/json") {
return res.json({ title: doc.title, updatedAt: doc.updatedAt, body: doc.markdown });
}
return res.render("doc", { doc });
});Vary: Accept header(RFC 9110 on Vary)를 보내면 cache가 각 representation을 따로 저장합니다. 많은 팀은 문서 path에 .md를 덧붙이는 식으로 명시적인 alternate URL도 제공합니다. 일부 client는 협상보다 이런 방식이 더 쉽기 때문입니다. HTML page에서 <link rel="alternate" type="text/markdown" href="...">로 연결해 두면 발견하기 쉽습니다. 이런 typed link는 Web Linking model을 따릅니다.
3단계: 에이전트는 구조화된 경로를 어떻게 찾는가?
찾지 못하는 것은 사용할 수 없습니다. 자동화된 client가 이미 찾는 위치에 진입점을 공개하세요.
- 사이트 root의
/llms.txt: 사이트가 제공하는 내용을 짧은 Markdown 요약으로 적고, 가장 유용한 페이지와 구조화된 버전으로 연결하는 링크를 넣습니다. llms.txt proposal을 따릅니다. - OpenAPI 문서 같은 API 설명을 개발자 문서와
/llms.txt에서 연결합니다. - HTML page 안의 alternate link로 Markdown 또는 JSON 버전을 가리킵니다.
- Sitemaps protocol이 정의한 형식으로 canonical content URL을 나열한 sitemap을 제공합니다.
간단한 llms.txt:
# Example App
> Example App helps teams plan projects. This file lists the best entry points for automated clients.
## Docs
- [Getting started](https://example.com/docs/getting-started.md): setup in five steps
- [API reference](https://example.com/openapi.json): OpenAPI description of the public API
## Policies
- [Automated access](https://example.com/docs/automated-access.md): limits and how to sign requests자동화된 client에 대한 규칙을 설명하는 page를 유지하세요. 어떤 route가 열려 있는지, 어떤 제한이 적용되는지, 그리고 client가 서명된 request로 자신을 식별해 더 나은 처리를 받으려면 어떻게 해야 하는지를 적습니다. 명확한 규칙은 추측을 협력으로 바꿉니다.
4단계: user agent string이 아니라 actor class로 라우팅하기
구조화된 response를 제공할 수 있게 되면, 기본적으로 누가 그것을 받을지 결정하세요. ad hoc하게 user agent string을 파싱하기보다 actor class와 그 basis를 사용합니다. AI operator도 자기 user agent를 공개하긴 합니다. OpenAI와 Anthropic이 crawler와 fetcher에 대해 그렇게 하고 있지만, string 매칭은 여전히 선언에 불과합니다.
type ActorClass = "human" | "human_ai" | "agent" | "unknown";
type Basis = "verified" | "declared" | "inferred" | "unknown";
function preferStructured(actor: ActorClass, basis: Basis, accept: string): boolean {
if (accept.includes("text/markdown") || accept.includes("application/json")) return true;
return actor === "agent" && (basis === "verified" || basis === "declared");
}두 가지 원칙이 이 방식을 안전하게 유지합니다.
- 명시적 요청이 항상 우선합니다. client가 Markdown 또는 JSON을 요청하면, class와 상관없이 그것을 제공합니다.
- 기본값은 아무것도 빼앗지 않습니다. Human 또는 Unknown 방문자는 언제나 전체 page를 받습니다. 에이전트에 구조화된 기본값을 주는 것은 형식을 바꾸는 것이지, 볼 수 있는 범위를 줄이는 것이 아닙니다.
5단계: AI 에이전트에는 어떤 rate limit이 적절한가?
rate limit은 actor context의 효과가 가장 분명하게 드러나는 지점입니다. 모두에게 하나의 제한을 두는 대신, class와 basis의 조합마다 합리적인 budget을 주세요.
| Actor and basis | Read routes | Notes |
|---|---|---|
| Agent, verified | Higher limit | Operator를 식별할 수 있고 책임을 질 수 있음 |
| Agent, declared or inferred | Standard limit | 주장이 검증 가능하지 않음 |
| Unknown | Standard limit | 기본값으로 벌칙을 주지 않음 |
| Human or Human + AI | Standard limit | 상호작용형 사용은 제한에 거의 닿지 않음 |
가장 안정적인 식별자를 기준으로 limit을 정하세요. verified agent라면 operator origin과 key identifier가 기준입니다. 그 밖에는 credential 또는 network 수준의 key를 사용합니다.
function limitKey(actor: { actor: ActorClass; basis: Basis; operator?: string }, ip: string, apiKey?: string) {
if (actor.actor === "agent" && actor.basis === "verified" && actor.operator) return `op:${actor.operator}`;
if (apiKey) return `key:${apiKey}`;
return `ip:${ip}`;
}
function limitFor(actor: { actor: ActorClass; basis: Basis }): number {
if (actor.actor === "agent" && actor.basis === "verified") return 600;
return 120;
}숫자는 자리표시자입니다. 관찰한 baseline을 기준으로 정하세요. client가 limit을 초과하면 429 Too Many Requests와 Retry-After header를 반환하고 자동화된 access page로 연결하세요. 잘 만들어진 agent는 속도를 낮추고 이를 읽습니다.
6단계: 작업은 authorization 뒤에 두기
읽기를 위한 구조화된 경로는 보안 model을 우회하는 경로가 아닙니다. 데이터를 변경하는 작업은 여전히 다음이 필요합니다.
- 애플리케이션이 발급한 credential로 수행하는 authentication,
- 그 특정 작업에 대한 authorization,
- route의 risk tier가 요구하는 추가 검사.
verified signature는 어떤 client가 호출했는지를 알려줍니다. 그 client가 특정 사용자를 대신해 행동해도 되는지는 알려주지 않습니다. 이 둘은 분리해서 보세요. signed AI agent request가 무엇을 증명하는지에 대한 글에서 그 차이를 설명합니다.
7단계: 효과 측정하기
먼저 observation mode로 구조화된 경로를 운영한 뒤, 다음을 비교하세요.
- 구조화된 형식을 사용하는 agent request의 비중,
- agent traffic의 server cost와 latency의 전후 비교,
- agent client의 error와 retry rate,
- signing guidance를 공개한 뒤 declared에서 verified로 이동하는 automated client 수.
이 수치들은 구조화된 경로가 제 역할을 하는지, 다음에 어디로 확장할지 보여줍니다.
8단계: 구조화된 경로를 version 관리하고 문서화하기
구조화된 경로는 공개 interface로 다루세요. 에이전트가 그것을 기반으로 동작하기 때문입니다. 몇 가지 습관이 신뢰성을 높입니다.
- URL을 안정적으로 유지하세요. 에이전트가
/docs/getting-started.md의 존재를 학습했다면 계속 동작해야 합니다. content가 이동하면404대신 새 위치로301를 반환하세요. - 갱신 시간을 포함하세요. JSON response에는
updatedAtfield를, Markdown에는Last-Modifiedheader를 넣어 에이전트가 적절히 cache하고 변경이 있을 때만 새로 받도록 합니다. - conditional requests를 지원하세요.
If-None-Match와If-Modified-Since에304 Not Modified로 응답하세요. content를 계속 확인하는 agent는 모든 것을 다시 내려받지 않고 재검증할 수 있을 때 훨씬 적은 비용이 듭니다. - breaking change는 version을 나누세요. JSON shape를 바꿔야 한다면 새 version path나 media type을 추가하고, 기존 것은 공개된 기간 동안 유지하세요.
- automated access page용 changelog를 공개하세요. 잘 동작하는 agent의 operator는 이를 읽고, 새 구조화된 route를 알릴 수 있는 장소가 됩니다.
app.get("/api/plans", async (req, res) => {
const plans = await loadPlans();
const etag = `"${plans.version}"`;
res.set("ETag", etag);
res.set("Cache-Control", "public, max-age=300");
if (req.get("if-none-match") === etag) return res.status(304).end();
return res.json({ updatedAt: plans.updatedAt, plans: plans.items });
});이 관행은 자체 frontend와 partner integration에도 도움이 됩니다. 안정적이고 cache 가능하며 문서화된 구조화된 경로는 어떤 program이든 애플리케이션과 작업하는 가장 쉬운 방법이 됩니다.
흔한 실수
- 에이전트와 사람에게 다른 사실을 제공하기. 구조화된 버전은 page와 같은 정보를 담아야 합니다. 내용이 달라지면 제품에 대해 assistant에게 질문하는 사용자가 혼란을 겪습니다.
- 구조화된 경로를 숨기기.
/llms.txt, API 문서, 또는 page 자체에서 연결되지 않으면 에이전트는 계속 HTML을 스크랩합니다. - 주장에 보상 주기. 높은 limit은 verified client에만 해당합니다. 복사한 user agent string이 더 큰 budget을 얻어서는 안 됩니다.
- cache를 잊기.
Vary: Accept가 없으면 cache가 browser에 Markdown을 제공할 수 있습니다.
Proof of Actor가 어디에 맞는가
Proof of Actor는 에이전트를 구조화된 콘텐츠로 라우팅하고 공정한 limit을 설정하는 데 필요한 actor class와 basis를 제공합니다. signed, declared, inferred agent를 탐지하고, 이들이 route를 어떻게 사용하는지 이해하도록 돕고, 라우팅 결정은 코드 안에 남겨 둡니다.
우리 팀과 함께 자신의 애플리케이션용 구조화된 경로를 설계하려면 early access를 신청하세요. API와 MCP 시나리오에 대해서는 각각 APIs와 MCP Servers를 보시고, who is calling your MCP server?에서 actor context가 Model Context Protocol tool call에 어떻게 적용되는지 읽어 보세요.
출처
- RFC 9110: HTTP Semantics: content negotiation(Section 12),
Vary(Section 12.5.5), conditional requests(Section 13),Retry-After(Section 10.2.3) - RFC 7763: The text/markdown Media Type: Markdown representation 제공
- RFC 8288: Web Linking: typed
alternatelinks - RFC 6585: Additional HTTP Status Codes, Section 4:
429 Too Many Requests - llms.txt proposal:
/llms.txtfile format - OpenAPI Specification: machine-readable API descriptions
- Sitemaps protocol: sitemap file format
- OpenAI: Overview of OpenAI Crawlers: OpenAI가 공개한 user agent와 IP range
- Anthropic: crawler and fetcher documentation: ClaudeBot, Claude-User, Claude-SearchBot
- Model Context Protocol specification (2026-07-28): 2026년 11월 기준 현재 MCP revision