AI Subroutines: 브라우저 탭 내부에서 실행되는 웹 자동화
요약
이 기술 기사는 웹 자동화의 한계를 극복하기 위해 브라우저 탭 내부에서 실행되는 'AI Subroutines'라는 새로운 접근 방식을 제안합니다. 기존 에이전트들이 API 호출에 의존할 때 발생하는 인증, CSRF 토큰, 세션 관리 등의 복잡한 문제를 해결하기 위해, 이 방식은 확장 프로그램(Extension)을 사용하여 브라우저의 네이티브 실행 컨텍스트 내에서 네트워크 요청 기록 및 재생을 수행합니다. 또한, 방대한 네트워크 트래픽 속에서 실제 작업에 필요한 핵심 API 호출만 선별적으로 추출하고 가중치 기반으로 순위화하여 LLM에게 제공하는 정교한 필터링 메커니즘도 설명합니다.
핵심 포인트
- 웹 자동화의 주요 난제는 단순한 API 호출이 아니라, 쿠키, CSRF 토큰, 서명 해시 등 복잡한 인증 및 세션 관리입니다.
- 외부 워커(Node/Playwright) 기반 스크래핑은 브라우저가 자체적으로 처리하는 동적 헤더와 세션을 재현하기 어렵습니다.
- 확장 프로그램 내에서 네트워크 요청을 인터셉트하고 재생하면, 같은 출처, 쿠키, TLS 세션 등 모든 인증 메커니즘이 자동으로 유지되어 신뢰성이 높아집니다.
- 단순히 네트워크를 기록하는 것을 넘어, 첫 번째 파티/세 번째 파티 여부, DOM 이벤트와의 시간적 상관관계, 요청 메서드 변화 등을 기준으로 가중치를 부여하여 핵심 API 호출만 선별적으로 추출합니다.
- 변동성 식별자(예: `queryId`, `doc_id`)가 포함된 경우, 네트워크 재플레이에 의존하기보다 DOM 상호작용을 우선하도록 AI 에이전트를 강제하는 안전장치가 마련되어 있습니다.
AI Subroutines: 브라우저 탭 내부에서 실행되는 웹 자동화
대부분의 웹 에이전트는 문제의 절반을 잘못 해결합니다. LLM 을 통해 X 에 게시하거나, Instagram DM 을 보내거나, LinkedIn 연결 요청을 한 번만 보낼 수는 있습니다. 하지만 이를 천 번씩 반복해야 할 때 경제학은 무너집니다: 호출당 토큰 수, 호출당 지연 시간, 호출당 비결정성. 아웃리치 (outreach), CRM 업데이트, 대량 게시에서 "에이전트가 이번에는 잘못된 버튼을 클릭했다"는 오류가 아닙니다. 이는 실패 모드입니다.
명백한 해결책은 UI 를 건너뛰고 사이트의 내부 API 를 직접 호출하는 것입니다. 이는 맞습니다. 그리고 대부분의 "API 만 호출하기" 프로젝트가 여기서 죽습니다. 왜냐하면 어려운 문제는 엔드포인트가 아니라 인증이기 때문입니다.
인증이 실제 어려운 문제입니다
인증된 웹 요청은 쿠키, 회전하는 CSRF 토큰, 세션 토큰, bearer 헤더, 반전 방지 비호 (anti-replay nonces), 사이트의 자체 JS 에서 요청 시 계산되는 지문 제한 파라미터, 및 요청 서명 해시를 포함합니다. 일부는 서버에서 설정됩니다. 일부는 브라우저에서 유도됩니다. 일부는 요청마다 회전합니다.
프로세스 외부 스크래퍼 — Node 워커, Playwright 워커, 클라우드 함수 — 는 모두를 별도의 대역으로 재구성해야 합니다. 이것이 사이트가 헤더를 회전하거나 새로운 서명 체계를 출시하는 순간을 깨뜨리는 것입니다. 대부분의 HAR 재생 도구들은 여기서 유효한 수명을 끝냅니다.
트릭: 확장기에서 기록하고 웹페이지 내부에서 재생하기
rtrvr 에서, 기록과 재생은 모두 사용자의 브라우저 내에서, 웹페이지 자체에서 발생합니다.
- 확장기는 탭이 작업을 수행하는 동안 네트워크 요청을 인터셉트합니다. 두 레이어: 페이지 스크립트가 실행되기 전에 설치된 MAIN-world
fetch/XHR패치와 Chrome 의webRequestAPI 를 CORS 와 서비스 워커 경로를 위해 상관관계 있는 대안으로. - 요청 본문 — FormData, Blob, 원시 바이트, JSON 만이 아닌 — 도 포착됩니다. - 스크립트가 나중에 실행될 때, 해당 요청은 페이지의 자체 실행 컨텍스트에서 디스패치됩니다. 같은 출처, 같은 쿠키, 같은 TLS 세션, 서명 헤더를 계산하는 JS 와 동일합니다.
Puppeteer 드라이버 없음. 헤드리스 워커 없음. 별도의 TLS 스택 없음. 브라우저는 항상 하는 일을 합니다: 쿠키를 연결하고, 사이트의 자체 JS 를 사용하여 헤더를 계산하며, 요청을 발송합니다.
인증, CSRF, 서명 및 지문은 모두 무료로 전파됩니다. 에이전트는 이를 어느 것도 만지지 않습니다. 키 추출 없음, 세션 재구성 없음, 프록시 회전 없음.
이것은 주석처럼 들립니다. 이것이 전체 아키텍처입니다.
네트워크 캡처의 순위와 편집
"네트워크만 기록하기"라는 말 안에는 숨겨진 두 번째 문제가 있습니다. 일반적인 분 1 분의 웹 브라우징은 탭당 수십에서 수백 개의 요청을 발사합니다 — 분석 비콘, RUM 핑, 기능 플래그 폴링, 세 번째 파티 픽셀, 프리페치, 미디어 조각, 핫 모듈 리로드 톡. 당신이 관심 있는 실제 API 호출은 종종 300 개 중 3 개입니다.
LLM 에 모든 것을 넘겨서 도구를 찾아보게 할 수 없습니다. 컨텍스트 윈도우에 맞지 않으며, 심지어 비용을 지불하여 확장하더라도 신호는 소음에 잠깁니다.
따라서 생성기가 무엇을 보기 전에 우리는 캡처를 순위와 편집합니다. 요청은 몇 가지 가중치된 신호에 따라 점수를 받습니다:
첫 번째 파티 vs 세 번째 파티 출처 (+20 / -15). 알려진 테lemetry 호스트 — Sentry, Segment, Hotjar, RUM, 일반적인 의심의 대상 — 은 평평한 -80 입니다. 클릭과 상관관계가 얼마나 잘 되든 그것은 도구가 아닙니다.
DOM 이벤트에 대한 시간적 상관관계(+28 800ms 이내, +16 2.5s 이내). APOST
클릭 후 40ms 가량 지연되어 "Send" 버튼을 누르는 것은 거의 확실하게 전송입니다.메서드와 페이로드 형태(변경된 POST/PUT/PATCH/DELETE: +35; GET: +5; 요청 본문 포함: +8; OPTIONS/HEAD/perf 엔트리는 −40).응답 품질(2xx: +12; 4xx+: −25; 비어있는 본문: +4). 변동적 작업 식별자(−18). URL 또는 본문에 GraphQL queryId, doc_id, operationHash 또는 빌드 특화 해시를 포함하는 요청들. 현재는 정상적으로 작동하지만 사이트 재배포 시 깨집니다.
구체적으로: 클릭 후 80ms 가량 지연되어 200 응답과 본문을 가진 첫 번째 파티 변형 POST 는 약 +83 입니다. 일반적인 분석 비콘은 −80 입니다. 그 사이의 모든 것은 순서를 정하고 상위 5 개가 살아남습니다. 이 5 개와 주변 DOM 상호작용이 생성기에 12,000 자의 컨텍스트로 렌더링됩니다; 초과 시 방문 URL 을 먼저, 다음으로 네트워크 후보, 마지막으로 DOM 힌트를 제거하고 다시 렌더링합니다.
순위 정렬 후에도 강력한 후보가 자동으로 재플레이 가치 있는 것은 아닙니다. 상위 요청이 변동적 작업 식별자를 포함하는 경우 — X 의 queryId, Meta 의 doc_id, 현재 배포에 고정된 GraphQL 작업 해시 — 플래너는 점수 무관하게 DOM 만 도구로 강제하고, 생성기는 이러한 값을 처음부터 노출하지 않도록 지시받습니다 (Do NOT expose or discover queryId/doc_id/operationHash values). 이는 초기에 잡아야 할 가장 유용한 실패 사례입니다: 네트워크 재플레이가 데모에서는 훌륭해 보이지만 사이트 배포 후 1 주 후에 조용히 깨집니다. DOM / 네트워크 / 하이브리드 결정 방식과 rtrvr.* 헬퍼 네임스페이스를 사용하는 생성된 코드는 문서에서 더 깊이 다룹니다.
이것은 기록 → 서브루틴이 실제로 작동하게 만드는 불광화 단계입니다. 인 페이지 실행은 인증을 무료로 해결합니다; 순위 정렬 — 변동적 ID 회로 차단기 — 가 생성기가 올바른 요청을 템플라테이션에 신뢰할 수 있게 선택하게 합니다.
서브루틴은 도구 호출이 아닌 매크로입니다
기록된 작업 — 서브루틴 — 은 에이전트의 도구 세트에서 search 와 fetch 옆에 callable 도구로 등록됩니다:
Instagram URL 시트 → sendInstagramDirectMessage({ url, message })
일일 콘텐츠 큐 → createXPost({ text, mediaUrl })
프로필 목록 → sendLinkedInConnectionRequest({ url, note })
에이전트를 500 행의 시트에 가리킵니다. 행마다 매개변수를 선택합니다. 서브루틴이 실행됩니다. LLM 은 행당 정확히 한 번 — 매개변수 선택을 위해 — 호출되며, 행동 자체는 스크립트입니다.
핫 패스에서 토큰 비용 0. 재플레이는 fetch 입니다, 추론이 아닙니다.결정적. 동일한 입력, 동일한 출력, 항상 동일합니다.낮은 탐지 표면. 요청들은 원래 요청을 보낸 동일한 사용자 세션에서 같은 헤더로, 같은 원천에서옵니다.자연어에서 LLM 호출 가능. 에이전트는 서브루틴을 다른 도구와 같은 방식으로 접근하며, 열려 있는 탭에서 매개변수를 추론합니다.
서브루틴 내부: rtrvr 헬퍼
서브루틴은 탭에서 실행되는 작은 비동기 JavaScript 함수입니다. 에이전트가 전달하는 매개변수 — 시트의 행, 목표 URL, 메시지 본문 — 은 코드 위에 const 선언으로 주입됩니다. 본체 내부에서, rtrvr.* 헬퍼 네임스페이스는 실제 사이트에서 필요한 일반적인 동작을 커버하며 취약한 선택자나 손으로 만든 fetch 스펀딩에 내려가지 않습니다:
| Helper | Use |
|---|---|
rtrvr.find({ role, name, text, placeholder }) | Find a semantic page target and return an opaque handle |
rtrvr.click(handleOrTarget) | Click a previously found handle or a semantic target |
rtrvr.type(handleOrTarget, value, { clear, submit }) | Type into inputs or rich contenteditable editors |
rtrvr.waitFor(targetOrFn, { timeoutMs }) | Wait for the next UI state, modal, composer, or control |
rtrvr.waitForUrl(match, { timeoutMs }) | Wait for navigation or route changes |
rtrvr.request(url, init) | Make authenticated in-page requests using the page context |
rtrvr.requestJson(url, init) | Same as request , but parses JSON when available |
rtrvr.getCsrfToken() | Read the current-page CSRF token |
rtrvr.getCookie(name) | Read a cookie from the current page |
A minimal LinkedIn "connect" Subroutine:
DOM when the UI is the stable contract, rtrvr.request
when the endpoint is. The generator mixes them as needed, and because everything runs in the page, cookies and CSRF tokens are just there — you read them, you don't rebuild them.
A few non-obvious implementation details worth knowing:
**Parameters bind as named function arguments, not by string concatenation.**A Subroutine body runs inside an AsyncFunction whose parameters are the declared Subroutine params plus rtrvr. Your code references url and message directly — they are not spliced into the source. No template-string injection surface, and param names can't collide with your own locals.
LinkedIn's messaging overlay and most Material 3 components mount interactive elements inside open shadow roots; naive rtrvr.find walks open shadow roots. document.querySelector misses them silently. When find fails it also returns the three closest candidates with their roles and names, so a broken recording tells you why instead of just timing out.
It reads rtrvr.request does CSRF discovery and header hygiene for you. ct0/XSRF-TOKEN/csrftoken from the cookie jar (and common meta-tag variants), injects x-csrf-token when missing, and strips replay-hostile headers from the recorded request (set-cookie, x-client-transaction-id, livepipeline-session) so one run's anti-replay nonce doesn't poison the next.
What this doesn't solve
We record HTTP. Sites that do real work over WebSockets, WebRTC, or heavy mid-flow client-side derivation need DOM actions interleaved into the Subroutine. That path is slower and less reliable — we treat it as a fallback, not the default.
Subroutines also need re-recording when a site meaningfully changes its API. That is the cost of not running an LLM on the hot path. In practice sites churn their APIs much less often than their DOM, so we think the tradeoff is correct — but it is a tradeoff, not a win on every axis.
How this is different from what else exists
Browser-Use and Stagehand keep the LLM in the runtime action path. Great for one-offs. Expensive and non-deterministic at scale.
Libretto (shipped last month, very similar intuition) moves the LLM to code-generation time and emits Playwright scripts. Big step in the right direction. But Playwright runs out-of-process, so the auth problem comes back — you inherit Playwright's session, not the user's, and every auth scheme a browser natively handles (SSO redirects, refresh-token rotation, service-worker-fetched tokens, DPoP, mTLS) becomes something you re-implement outside the browser.
여기서 새로운 것은 "런타임에 결정하기 대신 스크립트를 미리 생성하는 것"이 아닙니다. 사용자 브라우저 컨텍스트와 동일한 브라우저에서 실행되는 스크립트를 미리 생성하는 것입니다. 따라서 인증 문제는 별도의 문제가 되지 않습니다.
목표: 웹의 행동 공간 (action space) 을 위한 라이브러리
One Subroutine 는 도구입니다. Subroutines 의 라이브러리는 커버리지입니다.
오늘날, "에이전트는 원칙적으로 웹에서 모든 것을 할 수 있다"는 기술적으로는 참이지만 운영적으로는 쓸모가 없습니다. 원칙적으로 비즈니스를 실행할 수는 없습니다. 결여된 것은 에이전트가 실제로 지금, 실제 사이트에서 토큰 비용 없이 무엇을 할 수 있는지에 대한 공유적이고 결정적인 어휘입니다 — LinkedIn DM 전송, 일정 슬롯 예약, Zendesk 티켓 제출, Shopify 초안 주문 생성, HubSpot 연락처 업데이트 — 신뢰할 수 있게, 희망적으로가 아니라.
Subroutines 의 뒤에는 이 베팅이 있습니다. 우리는 템플릿과 데이터셋을 함께 공개적이고 커뮤니티 유지 관리되는 라이브러리를 구축하고 있으며, 이는 에이전트의 웹 행동 공간을 확장합니다. Instagram, X, LinkedIn 을 위한 미리 설치된 Subroutines 은 확장을 통해 씨앗으로 제공됩니다. 라이브러리는 더 많은 것을 출시하고 사용자가 자신의 것을 기록하고 공유하게 함으로써 성장합니다.
도움의 표면 자체도 계속 성장합니다. Google Sheets 에 행을 다시 작성하는 것, 일반적인 풍부 편집기를 조작하는 것, 구조화된 파일 I/O, 크로스 탭 조정, 후속 실행 스케줄링 — 우리가 추가하는 모든 도구는 더 많은 실제 웹 에이전트 작업이 결정적인 스크립트로 LLM 루프 대신에 작성될 수 있게 합니다. 방향은 구체적입니다: 웹 에이전트의 거의 전체 기능 (상단의 매개변수 선택, 중간부의 행동, 하단의 결과 작성) 은 결국 토큰 비용이 없는 Subroutines 으로 표현되며, LLM 은 판단이 실제로 필요한 곳에만 도달합니다.
어떤 때에나 하나의 사이트에서 유용한 일을 한 후 사이트가 헤더를 회전시켰을 때 glue 스크립트를 작성해 본 적이 있다면 — 그것은 페이지 내로 기록되어 실제로 살아남게 될 Subroutine 을 기다리는 것입니다.
이번 릴리스에도 포함된 것
Subroutines 와 함께 출시되는 것:
BYO ChatGPT 또는 Claude 구독 — 확장을 통해 OpenAI 나 Anthropic 으로 OAuth 를 수행하고 이미 지불한 계획에 따라 에이전트를 구동합니다. 별도의 API 키가 필요 없습니다.WhatsApp 제어 — 실행된 워크플로우를 저장소로 저장한 후, /run ,/schedule ,/trigger , 그리고/check_schedule_results 를 채팅 스레드에서 브라우저가 홈에서 실행되는 동안 실행합니다.
Knowledge Base + MCP 업그레이드 — KB 의 클라우드 스크랩하고 인덱싱, KB / 녹음물 / 스케줄 / Subroutines 을 위한 새로운 MCP 도구, 그리고 모든 클라우드 패널에 복사 준비된 API 스니펫으로 어떤 실행, KB 채팅, 또는 도구 호출이 하나의 curl 에서 가능합니다.
Rover + RoverBook — 사이트의 Rover 스크립트 태그를 떨어뜨려 에이전트 읽기 가능한 구조와 PostHog 스타일의 분석 뷰를 얻습니다: 방문, 궤적, AX 점수 (0–100), 에이전트 피드백, 그리고 지속 기억으로 돌아오는 에이전트가 처음부터 시작하지 않습니다.
그리고 나머지 표면: Knowledge Base + MCP 업그레이드 (클라우드 스크랩하고 인덱싱, KB / 녹음물 / 스케줄 / Subroutines 을 위한 새로운 MCP 도구, 모든 클라우드 패널에 복사 준비된 API 스니펫으로 어떤 실행, KB 채팅, 또는 도구 호출이 하나의 curl 에서 가능), .docx / .xlsx / .pptx 업로드를 채팅에서, 이메일 + 비밀번호 로그인, 접근성 트리의 클립핑 (⌘Ctrl+C 가 텍스트 스크래퍼보다 풍부한 트리를 가져온다), 기기 내 음성 입력, URL 의식 동적 템플릿, 그리고 UI + 성능 패스.
AI 자동 생성 콘텐츠
본 콘텐츠는 HN Claude Code Search의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기