10개의 AI 코딩 에이전트를 병렬로 실행할 수 있게 되었다. 각각에 이슈를 주고, 공유된 beads 데이터베이스를 향해 작업시킨다. 에이전트가 서브태스크를 만들고, 발견한 버그를 등록하고, 상태를 업데이트하고, 완료하면 이슈를 클로즈한다. 정말 생산적이다.
뭔가가 블로킹될 때까지는.
인간이 블로킹되면 뭔가를 말한다. Slack에 올리고, 스탠드업에서 보고하고, 누군가의 자리로 걸어간다. 에이전트는 그 어떤 것도 하지 않는다. 에이전트가 해결할 수 없는 의존관계에 부딪히면, 조용히 멈추거나 문제를 우회하려다 새 문제를 만드는 방식으로 작업하기 시작한다. 3개 에이전트가 같은 미해결 상류 태스크에 막혀 있어도, 4시간 동안 아무것도 출시되지 않은 이유가 궁금해질 때까지 모른다.
이것이 에이전트 개발에서의 트리아지 문제다. "더 나은 스탠드업 운영법"이 아니라, "불평하지 않는 자율 워커 플릿 전체에서 뭐가 막혀 있는지 어떻게 보는가"라는 문제다. Beadbox를 구축하면서 배운 것을 소개한다. beads의 실시간 대시보드로, 에이전트가 무엇을 하고 있는지, 무엇에 블로킹되어 있는지, 무엇이 가용해졌는지를 정확히 보여준다.
관심 있는 섹션으로 바로 이동:
- 에이전트가 블로킹 체인을 만드는 방식 -- 에이전트 작업 고유의 패턴
- 자동 의존관계 감지 -- 블록이 형성될 때 캐치
- CLI 퍼스트 트리아지 워크플로 -- 스크립트화 가능, 에이전트 실행 가능
- 실시간 가시성 -- 블록 발생 순간에 보기
- 트리아지 도구 평가 -- 무엇을 찾아야 하는가
- 슈퍼바이저 루프 -- 10개 이상 에이전트 일상 운영법
에이전트가 블로킹 체인을 만드는 방식
에이전트는 인간 팀과 다른 방식으로 의존관계 문제를 만든다. 장애 모드를 이해하는 것이 중요하다. 트리아지 대응이 다르기 때문이다.
에이전트는 의존관계를 사전에 모델링하지 않는다. 인간 아키텍트는 기능을 태스크로 분해하고 순서를 생각한다. 코딩 에이전트는 태스크를 받고, 작업을 시작하고, 구현 중에 아직 존재하지 않는 것이 필요하다는 걸 발견한다. 그 의존관계를 위한 새 이슈를 만들 수 있다. 인라인으로 구축하려다 혼란을 만들 수 있다. 그냥 멈출 수 있다. 이 결과 중 어느 것도 이슈 데이터베이스를 감시하지 않으면 보이지 않는다.
에이전트가 의존관계 그래프 업데이트보다 빠르게 작업한다. Agent-3가 Agent-7이 기다리던 태스크를 클로즈하지만, Agent-7은 10분 전에 블로커를 체크했으므로 모른다. 그 사이 Agent-7은 여전히 유휴 상태이거나 더 낮은 우선순위 태스크를 작업하고 있다. 언블록은 일어났지만 정보가 전파되지 않았다.
병렬 분해에서 순환 의존이 발생한다. 여러 에이전트가 동시에 작업을 분해하면, 단일 에이전트에게 보이지 않는 사이클을 만들 수 있다. Agent-1이 Task B에 의존하는 Task A를 생성. Agent-2가 Task C에 의존하는 Task B를 생성. Agent-3가 Task A에 의존하는 Task C를 생성. 각 의존관계는 로컬에서 합리적이었다. 사이클은 위에서 봐야만 보인다.
리소스 경합은 보이지 않는다. 두 에이전트가 같은 파일 수정이 필요하거나, 둘 다 스테이징 환경이 필요하거나, 공유 라이브러리가 안정 상태여야 한다. 의존관계가 등록되지 않았다. 어느 에이전트도 상대의 존재를 모르기 때문이다. 둘 다 느려질 뿐, 어느 쪽도 이유를 보고하지 않는다.
공통점: 에이전트는 보고하는 것보다 빠르게 블로킹 상황을 만든다. 슈퍼바이저(당신)에게는 누군가 플래그를 올리기를 기다리는 것이 아닌, 블록을 자동으로 표면화하는 도구가 필요하다.
자동 의존관계 감지
해법은 태스크 생성 시 만들어지고 지속적으로 체크되는, 명시적이고 쿼리 가능한 의존관계 데이터다. beads, 우리 에이전트 플릿이 사용하는 Git 네이티브 이슈 트래커에서 어떻게 보이는지 보여준다.
에이전트가 태스크 생성 시 의존관계를 기록:
# 에이전트가 태스크를 만들고 아직 없는 API가 필요함을 발견
API_TASK=$(bd create \
--title "Implement /api/v2/orders endpoint" \
--type task --priority 2 --json | jq -r '.id')
# 에이전트가 자기 태스크를 만들고 의존관계를 선언
UI_TASK=$(bd create \
--title "Build order history page" \
--type task --priority 2 --json | jq -r '.id')
bd dep add "$UI_TASK" "$API_TASK"
bd dep add는 CLI 한 줄이다. 어떤 AI 코딩 에이전트(Claude Code, Cursor, Copilot Workspace)든 실행할 수 있다. API 클라이언트 라이브러리도 인증 절차도 없다. 의존관계가 구조화된 데이터가 되어, 다른 에이전트나 스크립트에서 쿼리 가능해진다.
사이클 감지가 자동 실행:
# beads가 전체 의존관계 그래프의 사이클을 체크
bd dep cycles
# 사이클이 있을 때 출력:
# CYCLE DETECTED: beads-a1b -> beads-c3d -> beads-e5f -> beads-a1b
5,000개 의존관계 그래프에서 약 70ms. post-commit 훅이나 5분 cron으로 실행한다. 3개 에이전트가 독립적으로 의존관계 사이클을 만들어도, 셋 다 정체된 후 수시간 뒤에 발견하는 것이 아니라 수분 내 캐치할 수 있다.
블로킹된 모든 태스크를 한 명령으로 표면화:
bd blocked --json | jq -r '.[] |
"BLOCKED: \(.id) \(.title)\n waiting on: \(.blocked_by | join(", "))\n assignee: \(.owner // "unassigned")\n"'
출력:
BLOCKED: beads-x7q Build order history page
waiting on: beads-m2k Implement /api/v2/orders endpoint
assignee: agent-3
BLOCKED: beads-r4p Deploy staging environment
waiting on: beads-j9w Fix Docker build, beads-n1c Update TLS certificates
assignee: agent-7
Agent-3와 Agent-7이 막혀 있다는 것, 무엇에 막혀 있는지, 언블록에 무엇이 필요한지 알 수 있다. 이 전체 쿼리가 1만 건 이슈 데이터베이스에서 30ms 걸렸다.
브랜치 명명 규칙에서 블로킹된 PR 감지:
#!/bin/bash
# blocked-prs.sh: 의존관계가 머지되지 않은 PR 감지
for pr in $(gh pr list --json number,headRefName --jq '.[].headRefName'); do
ISSUE_ID=$(echo "$pr" | grep -oE 'beads-[a-z0-9]+')
[ -z "$ISSUE_ID" ] && continue
BLOCKERS=$(bd show "$ISSUE_ID" --json | jq -r '.blocked_by[]' 2>/dev/null)
for blocker in $BLOCKERS; do
BLOCKER_STATUS=$(bd show "$blocker" --json | jq -r '.status')
if [ "$BLOCKER_STATUS" != "closed" ]; then
echo "PR ($pr) blocked: $ISSUE_ID waiting on $blocker ($BLOCKER_STATUS)"
fi
done
done
20줄의 셸 스크립트. 로컬에서 실행, 로컬 데이터를 읽고, 에이전트의 어떤 PR이 아직 머지할 수 없는지와 이유를 알려준다.
CLI 퍼스트 트리아지 워크플로
에이전트 워크플로에서 트리아지는 미팅이 아니다. 루프로 실행되는 스크립트다. 슈퍼바이저(인간 또는 에이전트)가 막힌 것을 보고 각 항목에 대해 판단한다.
실제로 실행하는 트리아지 스크립트:
#!/bin/bash
# triage.sh: 에이전트 플릿 블로커 트리아지
echo "========================================="
echo "TRIAGE REPORT: $(date +%Y-%m-%d %H:%M)"
echo "========================================="
# 1. 뭐가 블로킹되어 있나?
echo -e "\n--- BLOCKED TASKS ---"
bd blocked --json | jq -r '.[] |
"[\(.priority)] \(.id) \(.title)
blocked by: \(.blocked_by | join(", "))
assignee: \(.owner // "unassigned")\n"'
# 2. 에이전트가 픽업할 수 있는 것은?
echo -e "\n--- READY (unblocked, open) ---"
bd ready --json | jq -r '.[] |
"[\(.priority)] \(.id) \(.title) (\(.owner // "unassigned"))"'
# 3. 조용해진 에이전트는?
echo -e "\n--- STALE IN-PROGRESS (no update in 2h) ---"
CUTOFF=$(date -v-2H +%Y-%m-%dT%H:%M:%S 2>/dev/null || date -d '2 hours ago' --iso-8601=seconds)
bd list --status=in_progress --json | jq -r --arg cutoff "$CUTOFF" '.[] |
select(.updated_at < $cutoff) |
"STALE: \(.id) \(.title) (last update: \(.updated_at), assignee: \(.owner // "unknown"))"'
# 4. 의존관계 건전성
echo -e "\n--- DEPENDENCY CYCLES ---"
bd dep cycles 2>&1 || echo "No cycles detected."
echo -e "\n--- FLEET STATS ---"
bd stats
인간 팀의 경우 "stale"은 48시간 업데이트 없음을 의미한다. 에이전트의 경우, 진행 중 태스크가 2시간 침묵하면 적신호다. 에이전트가 막혀서 보고하지 않거나, 크래시했거나. 어느 쪽이든 확인이 필요하다.
각 블로킹 항목에 대한 판단 트리:
- 다른 에이전트가 언블록할 수 있나? 블로킹 태스크의 우선순위를 올리고, 가용 에이전트를 할당한다.
- 의존관계가 가짜인가? 에이전트가 계획 시 과도하게 보수적인 의존관계를 등록하는 경우가 있다. 블록이 실제가 아니면 제거:
bd dep remove beads-x7q beads-m2k(x7q의 m2k 의존을 제거하여 x7q를 즉시 언블록). - 작업을 분할할 수 있나? 의존관계가 필요 없는 부분을 블로킹된 에이전트에게 작업시킨다. 나머지는 후속 태스크를 생성한다.
- 외부 블록인가? 인간만 해결할 수 있는 것(API 키, 디자인 결정, 접근 권한 부여). 태그하고, 예상 해결을 기록하고, 에이전트를 다른 레디 작업에 재할당한다.
옵션 2는 에이전트에서 빈번하게 발생한다. 계획 시점의 코드베이스 이해를 기반으로 의존관계를 모델링하지만, 구현이 시작되면 작업의 실제 형태가 드러나고 의존관계의 절반이 불필요했음이 밝혀진다.
에이전트 작업의 실시간 가시성
30분마다 트리아지 스크립트를 실행하면 갭이 남는다. 에이전트가 빠르게 작업하면, 체크 사이에 많은 일이 일어난다. 질문: 블록의 형성을 실시간으로 볼 수 있는가?
Beadbox의 방식:
beads 데이터베이스가 파일 시스템의 .beads/ 디렉토리에 존재한다. 에이전트가 실행하는 bd update, bd create, bd close 모두가 그 디렉토리에 쓴다. Beadbox가 fs.watch()로 감시하고, 밀리초 이내에 WebSocket을 통해 UI에 변경을 푸시한다.
실용적 효과: Agent-5가 터미널에서 bd update beads-x7q --status=closed를 실행한다. Beadbox 대시보드가 즉시 그 태스크를 클로즈드로 표시하고, 그것에 블로킹되어 있던 태스크가 새로 가용으로 점등한다. 명령을 실행하지 않고 캐스케이드가 보인다.
이것이 중요한 이유는 에이전트 작업이 버스트 형태로 발생하기 때문이다. 에이전트가 90초에 3개 태스크를 클로즈하고, 각각이 서로 다른 하류 작업을 언블록할 수 있다. 30초 새로고침 간격의 폴링 기반 대시보드는 혼란스러운 중간 상태를 보여준다. 서브초 전파는 일어나는 그대로의 전체 그림을 보여준다.
Beadbox를 사용하지 않더라도 파일 시스템 워치는 동작한다:
# beads 데이터베이스 변경을 감시하고 새 블록을 알림
# 참고: fswatch는 쓰기마다 발화한다. 프로덕션에서는 디바운스 필요
# (예: 트리거마다 sleep 2). 버스트 쓰기 시 노이즈 방지용.
fswatch -o .beads/ | while read; do
BLOCKED_COUNT=$(bd blocked --json | jq length)
if [ "$BLOCKED_COUNT" -gt 0 ]; then
echo "$(date): $BLOCKED_COUNT tasks currently blocked"
# ntfy, Slack webhook 또는 기타 알림 시스템에 파이프
fi
done
CI로 루프를 닫기:
# CI의 post-build 단계: 빌드 성공 시 이슈를 자동 클로즈
if [ "$BUILD_STATUS" = "success" ]; then
ISSUE_ID=$(echo "$BRANCH_NAME" | grep -oE 'beads-[a-z0-9]+')
if [ -n "$ISSUE_ID" ]; then
bd update "$ISSUE_ID" --status=closed
bd comments add "$ISSUE_ID" --author ci \
"Build passed. Commit: $COMMIT_SHA. Closing automatically."
fi
fi
CI가 그 이슈를 클로즈하면, 그것이 블로킹하던 모든 것이 언블록된다. 에이전트가 새 작업을 위해 bd ready를 감시하고 있으면, 언블록된 태스크를 자동으로 픽업한다. 일상적 언블록에 인간 루프 불필요.
이것이 상태를 추적하는 도구와 상태를 전파하는 도구의 차이다. 대부분의 프로젝트 관리 소프트웨어는 전자다: 카드를 업데이트하면 카드 색이 바뀐다. 전파란 하류 효과(의존 대상 언블록, 가용 작업 표면화, 진행률 롤업 업데이트)가 누구도 클릭하지 않고 일어나는 것이다.
에이전트 워크플로 트리아지 도구 평가
에이전트 플릿을 관리할 도구를 찾고 있다면, 요구사항이 인간 팀과 다르다.
필수: 에이전트가 호출할 수 있는 CLI. 이슈 트래커에 Web UI만 있으면 에이전트가 사용할 수 없다. 셸 명령을 실행해야 한다. bd create, bd update, bd blocked는 모두 한 줄이며 어떤 코딩 에이전트든 이미 실행 방법을 안다. REST API도 동작하지만, 인증 토큰, HTTP 클라이언트, 에러 핸들링이 필요하다. Unix 파이프가 더 간단하다.
필수: 쿼리 가능한 의존관계 그래프. 상태 레이블로서의 "Blocked"는 자동화에 쓸모없다. 스크립트가 그래프를 순회하고, 사이클을 감지하고, 레디한 것을 계산할 수 있도록 A가 B에 의존한다가 구조화된 데이터로 필요하다.
필수: 서브초 로컬 읽기. 에이전트가 가용 작업을 쿼리할 때 응답 시간이 중요하다. 쿼리당 2초 API 라운드트립에, 1분마다 폴링하는 10개 에이전트를 곱하면 측정 가능한 오버헤드가 된다. beads는 1만 건 이슈 데이터베이스에서 bd ready 결과를 30ms에 반환한다. 전부 로컬이기 때문이다.
있으면 좋음: 실시간 변경 전파. 에이전트가 시간당 50건 이슈를 생성하고 해결하면, 새로고침 간격이 아닌 변화하는 상태를 그대로 봐야 한다.
레드 플래그: "AI 기반 블로커 감지". 이슈 설명을 분석해서 블로커를 감지한다고 주장하는 도구는 거짓 양성을 만들고, 기록되지 않은 실제 블로커를 놓친다. 명시적 bd dep add 선언이 추론을 이긴다.
레드 플래그: 브라우저에서 트리아지가 필요한 도구. Web UI에서 태스크 하나를 언블록하는 데 5-15초 클릭. CLI에서 bd dep remove는 18ms. 블로킹된 태스크 50개에서 1분 vs 12분. 빠르게 움직이는 에이전트를 감독할 때, 트리아지 속도가 병목이다.
주요 도구의 블로킹 대응 비교
| 기능 | Jira | Linear | GitHub Issues | beads + Beadbox |
|---|---|---|---|---|
| 의존관계 트래킹 | 플러그인 (Advanced Roadmaps) | Relations (부분적) | Tasklist 참조 | 퍼스트 클래스 bd dep add |
| 블록 상태 자동 설정 | 수동 | 수동 | 수동 | 의존관계에서 자동 |
| 사이클 감지 | 없음 | 없음 | 없음 | 내장 (bd dep cycles) |
| 에이전트용 CLI | Jira CLI (서드파티) | Linear CLI (제한적) | gh (의존관계 없음) |
풀 (bd blocked, bd ready) |
| 실시간 전파 | Webhook (서버사이드) | Webhook (서버사이드) | Webhook (서버사이드) | fs.watch (서브초, 로컬) |
| 오프라인/로컬 동작 | 불가 | 불가 | 불가 | 가능 (임베디드 모드) |
| 에이전트 스크립팅 가능 | API + 인증 토큰 | API + 인증 토큰 | gh CLI |
bd CLI (Unix 파이프) |
슈퍼바이저 루프
하나의 프로젝트에서 10개 이상 AI 에이전트를 관리하는, 매일 실행하는 워크플로:
-
에이전트가 태스크 생성 시 의존관계를 선언. 전제조건이 있는 모든
bd create에 즉시bd dep add가 따른다. 태스크당 추가 CLI 호출 한 번이다. -
슈퍼바이저 에이전트가 30분마다
bd blocked를 실행. 새로 블로킹된 것이 있으면, 직접 블로커를 해결하거나(우선순위 변경, 재할당) 인간에게 플래그를 올린다. -
인간의 화면에서 Beadbox가 돌아간다. 대시보드가 블로킹된 태스크를 하이라이트한 전체 의존관계 그래프를 실시간으로 보여준다. 대부분의 경우, 자동화가 일상적 언블록을 처리한다. 불가능할 때(외부 의존, 아키텍처 결정, 접근 권한 부여), 인간이 즉시 문제를 보고 개입한다.
-
오래된 태스크를 적극적으로 플래그. 2시간 동안 진행 중 태스크를 업데이트하지 않은 에이전트는 막혔거나 크래시했다. 슈퍼바이저가 확인하고, 에이전트를 넛지하거나, 작업을 재할당하거나, 조사한다.
-
가짜 의존관계를 지속적으로 정리. 에이전트는 계획 중 의존관계를 과잉 선언한다. 구현이 작업의 실제 형태를 드러내면서, 슈퍼바이저(또는 에이전트 자체)가 불필요했던 의존관계를 제거한다. 깨끗한 그래프가 유용한 그래프다.
근본 원칙: 에이전트는 빠르지만 자기 인식이 없다. 다른 에이전트가 무엇을 하는지 모르고, 블로커가 해결된 것을 눈치채지 못하고, 막혀 있어도 불평하지 않는다. 슈퍼바이저의 역할은 이 모든 것을 연결하는 신경계가 되는 것이다. 자동으로 쿼리되고 시각적으로 렌더링되는 구조화된 의존관계 데이터가 이를 가능하게 한다.
Beadbox는 베타 기간 동안 무료. 에이전트가 무엇을 하고 있는지, 무엇이 블로킹되어 있는지, 무엇이 가용해졌는지를 실시간으로 보여준다.
brew tap beadbox/cask && brew install --cask beadbox
이미 beads를 사용하고 있다면, Beadbox는 임포트 단계 없이 기존 .beads/ 디렉토리를 읽는다. 사용해 보자.