---
title: "코딩 에이전트 여러 개가 한 저장소에서 일할 때: 잠금 대신 claim"
description: "Foxl Code가 병렬 코딩 에이전트끼리 같은 파일을 고치지 않게 하는 방법이에요. 잠금 없는 claim, glob 겹침 판정, 실행이 끝나면 풀리는 claim, D1 비용까지요."
author: "Foxl Team"
date_published: "2026-10-23"
date_modified: "2026-10-23"
canonical_url: "https://foxl.ai/ko/blog/parallel-coding-agents-file-claims"
markdown_url: "https://foxl.ai/ko/blog/parallel-coding-agents-file-claims/index.md"
image: "https://foxl.ai/blog/parallel-coding-agents-file-claims-cover.png"
social_image: "https://foxl.ai/blog/social/parallel-coding-agents-file-claims-ko.png"
content_type: "Deep dive"
topics: ["Coding agents","Multi-agent","Claude Code","Codex","Cloudflare D1"]
products: ["Foxl Code"]
---

<!-- Generated by scripts/blog-publishing.mjs; do not edit. -->

# 코딩 에이전트 여러 개가 한 저장소에서 일할 때: 잠금 대신 claim

> 코딩 에이전트를 여러 개 동시에 돌리면 머지할 때 부딪혀요. 서로 무엇을 고치는지 모르니까요. 이제 Foxl Code의 클라우드 에이전트는 고치려는 파일을 먼저 claim하라는 안내를 받아요. 겹치는 경로를 이미 claim한 다른 실행 중인 에이전트가 있으면, 그 에이전트가 누구인지 알려 줘요. claim은 겹쳐도 기록되고 아무것도 잠기지 않아요. 배포된 명령의 실제 출력, glob 규칙, 데이터베이스 비용, 그리고 아직 답을 모르는 질문, 곧 에이전트가 이 규칙을 정말 따르는지까지 정리했어요.

- Author: Foxl Team
- Published: 2026-10-23
- Last reviewed: 2026-10-23
- Reading time: 9 minutes
- Canonical HTML: [https://foxl.ai/ko/blog/parallel-coding-agents-file-claims](<https://foxl.ai/ko/blog/parallel-coding-agents-file-claims>)

![판 위에 파일 블록 다섯 개가 늘어선 아이소메트릭 그림이에요. 한 에이전트가 고칠 파일에는 파란 깃발이, 다른 에이전트가 고칠 파일에는 주황 깃발이 꽂혀 있고, 둘 다 claim한 가운데 파일에는 두 깃발 사이에 접힌 쪽지가 놓여 있어요.](<https://foxl.ai/blog/parallel-coding-agents-file-claims-cover.png>)

코딩 에이전트 여러 개가 한 저장소에서 동시에 일하면 머지할 때 부딪혀요. 다른 에이전트가 무엇을 고치는지 아무도 모르니까요. Foxl Code는 Claude Code나 Codex 같은 코딩 에이전트를 클라우드에서 돌려 주는 서비스인데, 이제 에이전트마다 claim 명령을 줘요. claim은 '이 파일은 내가 고칠 거예요'라고 미리 적어 두는 기록이에요. 에이전트는 파일을 고치기 전에 `foxl-agents claim <경로>`를 실행하라는 안내를 받아요. 같은 사람이 돌리는 다른 실행 중인 에이전트가 겹치는 경로를 이미 claim해 두었다면, 그 에이전트가 누구인지와 남긴 메모가 답으로 와요.

claim은 정보일 뿐 잠금이 아니에요. 겹치면 에이전트는 claim한 상대에게 필요한 변경을 보내거나, 상대가 claim을 풀 때까지 기다리거나, 사용자에게 물어보라는 안내를 받아요. claim은 에이전트의 실행이 끝나면 함께 끝나요. 이 기능은 Foxl v0.9.3-beta.5에 들어갔어요.

## 병렬 코딩 에이전트는 왜 부딪히나요?

에이전트마다 자기 체크아웃, 자기 브랜치에서 일하니 둘이 같은 파일을 고치는 걸 막는 게 없어요. Git은 두 번째 PR을 머지할 때 텍스트 충돌을 알려 주는데, 그때는 이미 두 에이전트가 일을 끝냈고 비용도 다 냈어요. 의미상의 충돌도 있어요. PR 두 개가 각각은 맞고 CI도 둘 다 통과했는데, 합치면 틀리는 경우예요. 이런 충돌은 사람이 둘 다 머지해서 빌드가 깨질 때까지 아무것도 알려 주지 않아요. claim이 이런 충돌을 잡을 수 있는 건 두 에이전트가 같은 경로를 건드릴 때뿐이에요.

무엇이 도움이 되는지는 저희 작업 방식에서 배웠어요. 저희는 Claude Code 세션 15-20개를 한 저장소에서 동시에 돌려서 Foxl을 만들고, 14일 동안 세션끼리 주고받은 메시지가 4,773건이었어요. 세션끼리 서로의 작업을 덮어쓰지 않게 된 건 공유 담당 목록을 쓰고 나서였어요. coordinator 세션 하나가 어느 세션이 어느 파일을 맡았는지 적어 두고, 여러 세션이 함께 건드리는 파일이라면 그 파일의 어느 부분을 맡았는지까지 적어요. 규칙은 남이 맡은 파일을 직접 고치지 말고, 그 파일을 맡은 세션에 변경을 보내는 거예요. 목록이 생기기 전에는 여러 세션이 같은 파일을 동시에 고치다 머지할 때야 알았어요. 목록에도 비용은 있었어요. 그 14일 동안 세션이 막힌 123건 가운데 33건이 coordinator의 답을 기다린 경우였어요.

저희 방식은 세션끼리 주고받는 메시지와, 누가 무엇을 맡았는지 적은 목록, 두 부분으로 되어 있어요. Foxl Code에는 메시지 쪽이 이미 있었어요. 클라우드 에이전트가 같은 사람이 돌리는 다른 실행 중인 에이전트 목록을 보고 메시지를 보낼 수 있는 `foxl-agents` 명령이에요. claim은 목록 쪽을 coordinator 없이 맡아요. 에이전트가 무엇을 건드릴지 직접 말하면, 겹치는지는 데이터베이스가 요청 한 번으로 알려 줘요.

## 왜 잠금이 아니라 claim인가요?

잠금은 모든 에이전트가 지키고 모든 에이전트가 풀어야 해요. 그런데 죽거나, 예산이 떨어지거나, 취소된 코딩 에이전트는 아무것도 풀지 않아요. 그러니 잠금에는 timeout이 필요하고, 실제 실행을 감당할 만큼 긴 timeout(저희 실행은 7시간을 넘길 수 있어요)은 그동안 모두를 막아요. 두 에이전트가 각자 상대 파일의 한 줄씩을 고쳐야 한다면, 둘은 영원히 서로를 기다려요(교착 상태). 거절당한 에이전트는 멈추는 것 말고 할 일도 없고요.

claim은 이 세 가지 문제를 하나씩 피해 가요. claim한 에이전트가 죽으면, 그 실행이 끝난 것으로 보이는 순간부터 그 claim은 겹침 판정에서 빠져요. 그래서 기다릴 timeout이 없어요. claim은 다른 claim과 겹쳐도 거절되지 않고 그대로 기록돼요. 대신 claim한 에이전트에게 상대 task의 id와 제목, 상대가 claim한 경로, 상대의 메모를 알려 줘요. 그리고 겹침을 만난 에이전트는 먼저 상대에게 필요한 변경을 보내라는 안내를 받아요. 그래서 서로의 파일이 필요한 두 에이전트에게도 기다리는 것 말고 할 수 있는 일이 생겨요. 물론 둘 다 기다리기를 고르는 걸 막지는 못해요. 모든 에이전트가 받는 안내문을 영어 원문 그대로 옮기면 이래요.

```
- Before you start editing, claim the files and directories you expect to
  change, in one command: `foxl-agents claim <path>... --note "<why>"`. A
  directory claims everything under it; `*`, `?` and `**` globs work. Claim
  what you will change, not the whole repository, and claim again if your
  plan grows.
- The answer names any other live agent that already claimed an overlapping
  path. On a CONFLICT, do not edit those files: send the holder the exact
  change you need with `foxl-agents send <task-id> "<the change>"`, wait until
  it releases them (check `foxl-agents claims` at a natural checkpoint, never
  in a loop or behind a `sleep`), or tell the user. A claim is information,
  not a lock.
- `foxl-agents release <path>...` drops a claim you no longer need. Every
  claim you hold is dropped when your run ends.
```

이 줄들은 CLI마다 읽는 사용자 수준 지침 파일 끝에 붙어요. Claude Code라면 CLAUDE.md, Codex, Kiro, Cursor, Hermes, OpenCode라면 AGENTS.md예요. CLI 여섯 개에 같은 장치가 닿아야 해서, `foxl-agents`는 MCP 서버가 아니라 셸 명령이에요. 어떤 코딩 CLI든 셸 명령은 실행할 수 있으니, 스크립트 하나면 여섯 CLI에서 모두 쓸 수 있고 CLI마다 서버를 불러오도록 따로 설정할 필요도 없어요.

## 에이전트는 무엇을 실행하고, 무엇을 보나요?

아래 그림 1은 배포된 명령을 실제로 실행한 출력이에요. 데모 저장소의 데모 task 두 개에 대해, 에이전트가 아니라 스크립트로 돌렸어요. task A는 rate limiting을 넣는 Codex task, task B는 users 엔드포인트에 페이지네이션을 넣는 Claude Code task예요. 서버 코드와 데이터베이스 구조, 명령은 실제 클라우드 환경과 똑같아요.

![터미널 출력이에요. task A가 src/api/middleware.ts와 src/api/routes.ts를 'adding a token bucket per API key'라는 메모와 함께 claim하고, 겹치는 경로가 없다는 답을 받아요. 이어서 task B가 src/api/ 디렉터리를 claim하자 task A, 제목, 두 파일, 메모를 담은 CONFLICT와, 변경을 보내거나 기다리거나 사용자에게 물으라는 안내가 나와요. 마지막 목록에는 두 task의 claim이 모두 있어요.](<https://foxl.ai/blog/parallel-coding-agents-file-claims-terminal.png>)

_그림 1. 배포된 명령을, Cloudflare의 Workers 런타임(workerd)을 로컬에 띄워 돌린 실제 서버에 대고 실행한 출력이에요(2026-10-23). task B의 claim도 기록됐어요._

task B는 끝에 슬래시를 붙여 `src/api/`를 입력했어요. 슬래시는 디렉터리라는 표시라서 서버가 `src/api/**`로 저장했어요. 슬래시 없이 입력했다면 명령이 직접 `/**`를 붙였을 거예요. 경로가 파일인지 디렉터리인지 알 수 있는 건 체크아웃 안에서 도는 명령뿐이거든요. 명령은 어느 디렉터리에서 실행하든 경로를 저장소 루트 기준으로 바꾸고, 체크아웃 밖의 경로는 서버에 보내기 전에 거절해요.

CONFLICT에는 task A의 두 파일과 메모가 나와요. task B는 이 메모를 보고 어떻게 할지 정할 수 있어요. 이 경우라면 task B는 middleware는 그대로 두고 거기 필요한 변경 하나만 task A에게 보내거나, 엔드포인트 자체의 handler부터 시작하고 task A가 claim을 풀면 다시 확인할 수 있어요.

## glob 두 개가 겹치는지는 어떻게 아나요?

두 claim 모두에 맞는 경로가 하나라도 있으면 겹친다고 봐요. 문법은 일부러 작게 만들었어요.

- 와일드카드가 없는 claim은 그 경로 하나만 뜻해요.
- `*`는 세그먼트(슬래시와 슬래시 사이) 하나 안에서만 맞아요.
- `?`는 정확히 한 글자에 맞아요.
- `**`만으로 된 세그먼트는 세그먼트 몇 개에든 통째로 맞아요.
- 나머지 글자는 모두 글자 그대로 비교해요. 그래서 `[id].tsx`는 문자 클래스가 아니라 파일 이름이에요.

배포된 함수를 예시 짝에 돌리면 이렇게 나와요.

| claim A | claim B | 겹침 |
| --- | --- | --- |
| `src/api/**` | `src/api/users.ts` | 겹쳐요 |
| `src/*.ts` | `src/api/users.ts` | 안 겹쳐요. `*`는 세그먼트 하나 안에서만 맞아요 |
| `src/**/*.test.ts` | `src/api/*.ts` | 겹쳐요. 둘 다 `src/api/x.test.ts`에 맞아요 |
| `docs/**` | `src/**` | 안 겹쳐요 |
| `src/app/[id]/page.tsx` | `src/app/a/page.tsx` | 안 겹쳐요. `[id]`는 글자 그대로예요 |
| `**/*.md` | `docs/guide.md` | 겹쳐요 |

겹친다는 이유로 거절하지는 않지만, 모양 때문에 거절하는 claim은 있어요.

- `**`를 포함하고 와일드카드로만 된 glob. `**`나 `**/*`처럼 저장소 전체를 잡게 되거든요.
- `..` 세그먼트, 역슬래시, 제어 문자가 들어간 경로.
- 맨 앞의 `!`. 여기서는 부정이 아니고, 부정처럼 보여서도 안 되니까요.

task 하나는 claim을 50개까지 할 수 있고, claim 하나는 200자, 메모는 280자까지예요.

claim이 아무리 특이하게 생겨도 서버 CPU를 일정 이상 쓰지 않도록, 비교에는 상한이 있어요. 와일드카드가 없는 앞쪽 세그먼트가 서로 다른 두 claim은(`docs/**`와 `src/**`처럼) 탐색하지 않고 바로 안 겹친다고 판정해요. 나머지는 두 claim을 세그먼트 단위로, 세그먼트 안에서는 글자 단위로 짝지어 보는 탐색으로 비교하고, 이미 계산한 짝은 다시 계산하지 않아요(memoize). 요청 하나에서 하는 비교는 모두 합쳐 50,000 step 안에서만 하고, 그 안에 확인하지 못한 짝은 버리지 않고 "겹칠 수 있음"으로 알려요. 다 확인하지 못했을 때는 에이전트에게 알리는 쪽을 택하는 거예요.

## claim은 언제 끝나나요?

claim은 실행 하나에 속해요. 실행이 끝나면, task의 상태를 관리하는 코드가 그 실행을 끝났다고 기록하는 바로 그 단계에서 실행의 claim도 모두 지워요. 그 단계까지 가지 못하고 멈춘 실행도 있으니, claim을 읽을 때 두 가지를 더 걸러요. claim한 에이전트가 더는 실행 중이 아니면 무시하고, 실행이 이어질 수 있는 최대 시간인 7시간 20분보다 오래된 claim도 무시해요. claim이 보이는 범위는 메시지와 같아요. 같은 사람의 실행 중인 에이전트 가운데, 같은 GitHub App 설치로 같은 저장소에서 일하는 에이전트끼리만 서로의 claim을 봐요.

![에이전트 둘과 claim 테이블 하나가 있어요. 에이전트 A(task A)가 파일 둘을 claim하고 겹침이 없다는 답을 받아요. 에이전트 B(task B)가 그 파일들을 감싼 디렉터리를 claim하면 B의 claim도 기록되고, 에이전트 A를 가리키는 CONFLICT가 돌아와요. 에이전트 B에게는 A에게 변경 보내기, A가 풀 때까지 기다리기, 사용자에게 묻기라는 세 가지 길이 있어요. 실행이 끝나면 그 실행의 claim은 지워져요.](<https://foxl.ai/blog/parallel-coding-agents-file-claims-flow.png>)

_그림 2. 겹친다는 사실은 claim한 에이전트에게 알려 줘요. 겹친다고 거절되는 것도, 잠금을 기다리는 것도 없어요._

## 무료 데이터베이스 플랜에서 얼마나 드나요?

claim은 테이블 하나에 저장하고, 인덱스는 (사용자, 저장소, 생성 시각) 하나뿐이에요. 이 테이블을 폴링하는 곳도 없어요. claim을 읽을 때는 언제나 최근 7시간 20분 안에 만든 claim만 묻는데, 생성 시각이 인덱스의 마지막 열이라 그보다 오래된 행은 데이터베이스가 훑는 범위 밖에 있어요. 그래서 아무도 풀지 않은 오래된 claim이 쌓여도 읽기 비용은 한 행만 늘어요. 1,000개일 때와 2,000개일 때를 모두 재 봤어요. claim 테이블이 커질 수 있다면 이 부분은 가져다 쓸 만해요.

이렇게까지 재는 건 Foxl이 Cloudflare의 무료 플랜 위에서 돌아가기 때문이에요. 이 플랜에서는 계정의 모든 D1 데이터베이스(Cloudflare의 데이터베이스)가 하루에 읽기 500만 행, 쓰기 10만 행을 나눠 쓰고, Worker 요청은 계정 전체가 하루 10만 개까지예요. 아래 숫자는 로컬에 띄운 workerd의 D1에서 잰 값이고, 실서비스 D1의 지표로는 아직 확인하지 않았어요.

| 호출 | 읽는 행 | 쓰는 행 |
| --- | --- | --- |
| 경로 N개 claim | 저장소에 실행 중인 claim 하나당 2행쯤, 경로 하나당 1행쯤, 고정으로 몇 행 | 경로 하나당 2행 |
| claim 목록 | 1행 + 실행 중인 claim 하나당 2행 | 0행 |
| release | 경로 하나당 1행쯤 | 지운 행 하나당 1행 |
| claim이 없던 실행이 끝날 때 | 0행 | 0행 |

모든 사용자를 합쳐 하루에 클라우드 task가 50개쯤이고 task마다 claim을 두 번 부른다면, 호출은 100번이에요. 호출 하나가 code.foxl.ai를 거쳐 내부 연결로 서버에 한 번 더 넘어가기 때문에 Worker 요청으로는 200개쯤이고, 무료 플랜의 하루 10만 개 가운데 0.2%예요. 이 호출이 모두 바쁜 사용자의 최악의 경우(한 저장소에서 에이전트 10개가 claim을 5개씩 가진 경우)만큼 든다고 쳐도, 하루에 읽기는 11,000행쯤(500만 행의 0.2%), 쓰기는 830행쯤(10만 행의 0.8%)이에요.

## 에이전트들은 정말 규칙을 따르나요?

아직 몰라요. 이번 단계는 그걸 알아내려고 만들었어요. 2026-10-23 기준으로 실서비스의 claim 테이블은 비어 있어요. 기능이 배포된 뒤로 클라우드 task가 아직 하나도 시작되지 않았거든요. 저희 로컬 세션도 같은 규칙을 지키게 하려면 coordinator가 필요했으니, 서로 다른 CLI 여섯 개가 안내문에 적혀 있다는 이유만으로 고치기 전에 claim하고 겹치면 물러설 거라고 가정하지 않아요.

겹칠 때마다 감사 기록이 남아요. 같은 task가 같은 상대와 10분 안에 다시 겹치면, 서버 인스턴스 하나당 한 번만 남겨요. 에이전트가 다른 에이전트에게 보낸 메시지도 기록돼요. 겹친 뒤에 에이전트가 상대에게 메시지를 보냈는지는 이 기록으로 알 수 있어요. 자기 claim을 풀었는지는 claim 테이블을, 그냥 파일을 고쳤는지는 PR의 diff를 봐야 해요.

다음 단계는 그 답에 달려 있어요. 에이전트들이 claim을 지킨다면, Foxl Code에서 요청 하나를 에이전트 여럿으로 나누는 부분이 하위 작업마다 파일 목록을 주고, 겹치는 하위 작업은 동시에가 아니라 차례로 돌리게 할 계획이에요.

병렬 에이전트를 직접 돌린다면 이 세 가지는 지금 바로 가져다 쓸 만해요. claim은 겹쳐도 항상 기록하고, 겹치면 상대가 누구인지와 상대의 메모를 함께 돌려주고, claim은 실행이 끝날 때 함께 지우는 거예요. 에이전트 루프 자체는 Foxl Code를 처음 낼 때 쓴 [글(영어)](<https://foxl.ai/blog/foxl-code-autonomous-agents>)에 있어요. 단계마다 결과를 그대로 믿지 않고 목표에 맞춰 계속 바로잡는 루프예요.

## References and further reading

1. [Cloudflare D1 요금과 무료 플랜 한도](<https://developers.cloudflare.com/d1/platform/pricing/>) - Documentation
2. [Foxl v0.9.3-beta.5 릴리스](<https://github.com/foxl-ai/foxl/releases/tag/v0.9.3-beta.5>) - Release