메뉴
HN
Hacker News 5일 전

대규모 AI 코드 리뷰 오케스트레이션

IMP
8/10
핵심 요약

클라우드플레어(Cloudflare)는 하나의 범용 AI 모델에 의존하는 기존 방식의 한계를 극복하고자 보안, 성능, 코드 품질 등을 담당하는 최대 7개의 전문 AI 에이전트를 실행하는 오케스트레이션 시스템을 구축했습니다. 이 시스템은 수만 건의 병합 요청(Merge Request)을 검토하며 실제 버그와 취약점을 고도로 정확하게 찾아내고 심각한 문제 발견 시 병합을 적극적으로 차단합니다. 이 글은 방대한 코드베이스와 다양한 내부 표준을 유연하게 지원하기 위해 고안한 플러그인 기반 아키텍처와 CI/CD 파이프라인 내 LLM 통합 과정의 구체적인 기술적 고민을 깊이 있게 다룹니다.

번역된 본문

대규모 AI 코드 리뷰 오케스트레이션 2026-04-20 작성자: Ryan Skidmore 읽는 데 걸리는 시간: 19분 이 게시물은 중국어(간체), 일본어, 한국어 및 중국어(번체)로도 제공됩니다.

코드 리뷰는 버그를 잡고 지식을 공유하는 훌륭한 메커니즘이지만, 엔지니어링 팀의 병목 현상을 유발하는 가장 확실한 방법 중 하나이기도 합니다. 병합 요청(Merge Request)이 대기열에 머물고, 리뷰어가 결국 다른 업무에서 벗어나 문맥을 전환하여 diff(변경 사항)를 읽고, 변수명에 대한 약간의 지적을 남기고, 작성자가 이에 답변하는 과정이 무한히 반복됩니다. 사내 프로젝트 전반을 살펴보면, 첫 리뷰를 받기까지의 대기 시간 중앙값은 종종 몇 시간 단위로 측정되었습니다.

AI 코드 리뷰 실험을 처음 시작했을 때 우리는 아마도 다른 대부분의 사람들이 선택했을 법한 길을 걸었습니다. 몇 가지 다른 AI 코드 리뷰 도구들을 시도해 보았고, 이러한 도구들 중 상당수가 꽤 잘 작동하며 심지어 훌륭한 수준의 맞춤 설정 및 구성 기능을 제공한다는 것을 발견했습니다. 하지만 불행히도 계속해서 반복해서 등장했던 하나의 공통된 문제점은 Cloudflare 규모의 조직에 필요한 수준의 유연성과 맞춤 설정을 제공하지 못한다는 것이었습니다.

그래서 우리는 다음으로 가장 명백한 길로 넘어갔고, 그것은 바로 git diff를 가져와서 대충 작성된 프롬프트에 쑤셔 넣은 다음, 대규모 언어 모델(LLM)에게 버그를 찾아달라고 요청하는 것이었습니다. 그 결과는 여러분이 예상할 수 있듯이 엄청나게 쓸데없는 노이즈로 가득했습니다. 모호한 제안이 쏟아져 나왔고, 존재하지 않는 문법 에러를 환각(Hallucination) 현상으로 만들어냈으며, 이미 에러 처리가 되어 있는 함수에 "에러 처리를 추가해 보는 건 어떨까요?"라는 쓸데없는 조언을 남기기도 했습니다. 우리는 단순한 요약 접근 방식, 특히 복잡한 코드베이스에서는 원하는 결과를 얻을 수 없다는 사실을 매우 빠르게 깨달았습니다.

처음부터 거대한 단일 코드 리뷰 에이전트를 구축하는 대신, 우리는 오픈소스 코딩 에이전트인 OpenCode를 중심으로 CI 네이티브 오케스트레이션 시스템을 구축하기로 결정했습니다.

오늘날 Cloudflare의 엔지니어가 병합 요청을 열면, 조율된 다양한(AI 에이전트의) 스모르고스보드(다양한 종류의 모음)로부터 최초의 1차 리뷰를 받게 됩니다. 거대하고 범용적인 단일 프롬프트를 가진 하나의 모델에 의존하는 대신, 우리는 보안, 성능, 코드 품질, 문서화, 릴리즈 관리 및 사내 엔지니어링 규정(Engineering Codex) 준수 여부를 다루는 최대 7개의 전문 리뷰어를 실행합니다. 이 전문가들은 코디네이터 에이전트(Coordinator agent)에 의해 관리됩니다. 이 코디네이터는 중복된 결과를 제거하고, 문제의 실제 심각도를 판단하며, 단일하고 구조화된 리뷰 코멘트를 게시합니다.

우리는 이 시스템을 수만 건의 병합 요청에 걸쳐 내부적으로 운영해 왔습니다. 이 시스템은 깔끔한 코드는 승인하고, 실제 버그는 놀라운 정확도로 표시하며, 진정으로 심각한 문제나 보안 취약점을 발견하면 능동적으로 병합을 차단합니다. 이는 '코드 오렌지: 작게 실패하기(Code Orange: Fail Small)' 이니셔티브의 일환으로 엔지니어링 복원력을 향상시키는 우리의 여러 방법 중 하나일 뿐입니다.

이 글은 우리가 이 시스템을 어떻게 구축했는지, 어떤 아키텍처를 최종적으로 도입했는지, 그리고 LLM을 CI/CD 파이프라인의 핵심 경로에 배치하고, 더 나아가 코드를 배포(Ship)하려는 엔지니어의 업무 흐름 한가운데에 투입할 때 직면하게 되는 구체적인 엔지니어링 문제들에 대해 깊이 있게 다루는 심층 분석 게시물입니다.

아키텍처: 달까지 뻗어나가는 플러그인(Plugins all the way to the moon) 수천 개의 리포지토리에서 실행되어야 하는 내부 도구를 구축할 때, 버전 관리 시스템이나 AI 제공자(Provider)를 코드에 하드코딩하는 것은 불과 6개월 뒤에 전체를 다시 작성하게 만드는 확실한 지름길입니다. 우리는 오늘날 GitLab을 지원해야 하지만 내일은 어떤 시스템을 지원하게 될지 알 수 없는 상황에서, 서로 다른 AI 제공자와 서로 다른 내부 표준 요구 사항을 각 구성 요소가 서로에 대해 알 필요 없이 유연하게 지원할 수 있어야 했습니다. 우리는 진입점(Entry point)이 리뷰 실행 방식을 정의하기 위해 함께 조합되는 플러그인들에게 모든 구성을 위임하는, 조합 가능한(Composable) 플러그인 아키텍처를 기반으로 시스템을 구축했습니다. 병합 요청이 리뷰를 트리거할 때의 실행 흐름은 다음과 같습니다: 각 플러그인은 세 가지 라이프사이클 단계(Lifecycle phase)를 가진 ReviewPlugin 인터페이스를 구현합니다. 부트스트랩(Bootstrap) 훅은 동시에 실행되며 치명적이지 않은(Non-fatal) 오류를 허용합니다. 즉, 템플릿을 가져오는 데 실패하더라도 리뷰는 계속 진행됩니다. 구성(Configure) 훅은 순차적으로 실행되며 치명적(Fatal)입니다. 왜냐하면 VCS 제공자가 GitLab에 연결할 수 없다면 작업을 계속할 필요가 없기 때문입니다. 마지막으로 postConfigure 훅이 실행됩니다.

원문 보기
원문 보기 (영어)
Orchestrating AI Code Review at scale 2026-04-20 Ryan Skidmore 19 min read This post is also available in 简体中文 , 日本語 , 한국어 and 繁體中文 . Code review is a fantastic mechanism for catching bugs and sharing knowledge, but it is also one of the most reliable ways to bottleneck an engineering team. A merge request sits in a queue, a reviewer eventually context-switches to read the diff, they leave a handful of nitpicks about variable naming, the author responds, and the cycle repeats. Across our internal projects, the median wait time for a first review was often measured in hours. When we first started experimenting with AI code review, we took the path that most other people probably take: we tried out a few different AI code review tools and found that a lot of these tools worked pretty well, and a lot of them even offered a good amount of customisation and configurability! Unfortunately, though, the one recurring theme that kept coming up was that they just didn’t offer enough flexibility and customisation for an organisation the size of Cloudflare. So, we jumped to the next most obvious path, which was to grab a git diff, shove it into a half-baked prompt, and ask a large language model to find bugs. The results were exactly as noisy as you might expect, with a flood of vague suggestions, hallucinated syntax errors, and helpful advice to "consider adding error handling" on functions that already had it. We realised pretty quickly that a naive summarisation approach wasn't going to give us the results we wanted, especially on complex codebases. Instead of building a monolithic code review agent from scratch, we decided to build a CI-native orchestration system around OpenCode , an open-source coding agent. Today, when an engineer at Cloudflare opens a merge request, it gets an initial pass from a coordinated smörgåsbord of AI agents. Rather than relying on one model with a massive, generic prompt, we launch up to seven specialised reviewers covering security, performance, code quality, documentation, release management, and compliance with our internal Engineering Codex. These specialists are managed by a coordinator agent that deduplicates their findings, judges the actual severity of the issues, and posts a single structured review comment. We've been running this system internally across tens of thousands of merge requests. It approves clean code, flags real bugs with impressive accuracy, and actively blocks merges when it finds genuine, serious problems or security vulnerabilities. This is just one of the many ways we’re improving our engineering resiliency as part of Code Orange: Fail Small . This post is a deep dive into how we built it, the architecture we landed on, and the specific engineering problems you run into when you try to put LLMs in the critical path of your CI/CD pipeline, and more critically, in the way of engineers trying to ship code. The architecture: plugins all the way to the moon When you are building internal tooling that has to run across thousands of repositories, hardcoding your version control system or your AI provider is a great way to ensure you'll be rewriting the whole thing in six months. We needed to support GitLab today and who knows what tomorrow, alongside different AI providers and different internal standards requirements, without any component needing to know about the others. We built the system on a composable plugin architecture where the entry point delegates all configuration to plugins that compose together to define how a review runs. Here is what the execution flow looks like when a merge request triggers a review: Each plugin implements a ReviewPlugin interface with three lifecycle phases. Bootstrap hooks run concurrently and are non-fatal, meaning if a template fetch fails, the review just continues without it. Configure hooks run sequentially and are fatal, because if the VCS provider can't connect to GitLab, there is no point in continuing the job. Finally, postConfigure runs after the configuration is assembled to handle asynchronous work like fetching remote model overrides. The ConfigureContext gives plugins a controlled surface to affect the review. They can register agents, add AI providers, set environment variables, inject prompt sections, and alter fine-grained agent permissions. No plugin has direct access to the final configuration object. They contribute through the context API, and the core assembler merges everything into the opencode.json file that OpenCode consumes. Because of this isolation, the GitLab plugin doesn't read Cloudflare AI Gateway configurations, and the Cloudflare plugin doesn't know anything about GitLab API tokens. All VCS-specific coupling is isolated in a single ci-config.ts file. Here is the plugin roster for a typical internal review: Plugin Responsibility @opencode-reviewer/gitlab GitLab VCS provider, MR data, MCP comment server @opencode-reviewer/cloudflare AI Gateway configuration, model tiers, failback chains @opencode-reviewer/codex Internal compliance checking against engineering RFCs @opencode-reviewer/braintrust Distributed tracing and observability @opencode-reviewer/agents-md Verifies the repo's AGENTS.md is up to date @opencode-reviewer/reviewer-config Remote per-reviewer model overrides from a Cloudflare Worker @opencode-reviewer/telemetry Fire-and-forget review tracking How we use OpenCode under the hood We picked OpenCode as our coding agent of choice for a couple of reasons: We use it extensively internally, meaning we were already very familiar with how it worked It’s open source, so we can contribute features and bug fixes upstream as well as investigate issues really easily when we spot them (at the time of writing, Cloudflare engineers have landed over 45 pull requests upstream!) It has a great open source SDK , allowing us to easily build plugins that work flawlessly But most importantly, because it is structured as a server first, with its text-based user interface and desktop app acting as clients on top. This was a hard requirement for us because we needed to create sessions programmatically, send prompts via an SDK, and collect results from multiple concurrent sessions without hacking around a CLI interface. The orchestration works in two distinct layers: The Coordinator Process: We spawn OpenCode as a child process using Bun.spawn . We pass the coordinator prompt via stdin rather than as a command-line argument, because if you have ever tried to pass a massive merge request description full of logs as a command-line argument, you have probably met the Linux kernel's ARG_MAX limit. We learned this pretty quickly when E2BIG errors started showing up on a small percentage of our CI jobs for incredibly large merge requests. The process runs with --format json , so all output arrives as JSONL events on stdout : const proc = Bun.spawn( ["bun", opencodeScript, "--print-logs", "--log-level", logLevel, "--format", "json", "--agent", "review_coordinator", "run"], { stdin: Buffer.from(prompt), env: { ...sanitizeEnvForChildProcess(process.env), OPENCODE_CONFIG: process.env.OPENCODE_CONFIG_PATH ?? "", BUN_JSC_gcMaxHeapSize: "2684354560", // 2.5 GB heap cap }, stdout: "pipe", stderr: "pipe", }, ); The Review Plugin: Inside the OpenCode process, a runtime plugin provides the spawn_reviewers tool. When the coordinator LLM decides it is time to review the code, it calls this tool, which launches the sub-reviewer sessions through OpenCode's SDK client: const createResult = await this.client.session.create({ body: { parentID: input.parentSessionID }, query: { directory: dir }, }); // Send the prompt asynchronously (non-blocking) this.client.session.promptAsync({ path: { id: task.sessionID }, body: { parts: [{ type: "text", text: promptText }], agent: input.agent, model: { providerID, modelID }, }, }); Each sub-reviewer runs in its own OpenCode session with its own agent prompt. The coordinator doesn't see or control what tools the sub-reviewers use. They