返回博客

并行开发中的阻塞任务分类

现在你可以同时运行 10 个 AI 编程智能体。给每个一个 issue,指向一个共享的 beads 数据库,让它们工作。智能体创建子任务、提交发现的 bug、更新状态、完成后关闭 issue。真正有生产力。

直到某些东西被阻塞。

当人类被阻塞时,会说出来。在 Slack 里说、在站会上提、走到同事桌前。智能体不会做任何这些。智能体遇到无法解决的依赖时,要么默默停滞,要么开始绕路,而绕路往往制造更多问题。三个智能体可能卡在同一个未解决的上游任务上,而你直到四个小时后好奇为什么什么都没交付时才会知道。

这就是智能体开发中的分类问题。不是"如何开更好的站会",而是"如何看到一群不会抱怨的自主工作者中什么卡住了"。这里分享的是我们在构建 Beadbox 过程中学到的经验。Beadbox 是 beads 的实时仪表盘,它让你实时看到智能体在做什么、被什么阻塞、以及什么刚变为可用。

直接跳到你关心的部分:

智能体如何创建阻塞链

智能体产生依赖问题的方式和人类团队不同。理解这些失败模式很重要,因为对应的分类响应也不同。

智能体不会预先建模依赖。 人类架构师会将功能分解为任务并思考顺序。编程智能体接到任务后开始工作,在实现过程中发现需要一些尚不存在的东西。它可能会为那个依赖创建新 issue。可能会试图内联构建并制造混乱。也可能直接停止。除非你在监视 issue 数据库,否则这些结果都不可见。

智能体工作速度快于依赖图更新。 智能体 3 关闭了智能体 7 正在等待的任务,但智能体 7 不知道,因为它在 10 分钟前就检查过阻塞情况了。而此时智能体 7 仍然空闲或在做优先级更低的事。解除阻塞已经发生了,但信息没有传播。

并行分解产生循环依赖。 当多个智能体同时分解工作时,可能创建出单个智能体看不到的循环。智能体 1 创建任务 A 依赖任务 B。智能体 2 创建任务 B 依赖任务 C。智能体 3 创建任务 C 依赖任务 A。每个依赖在局部看都合理。循环只有从上方才能看到。

资源争用是隐形的。 两个智能体都需要修改同一个文件,或都需要 staging 环境,或都需要同一个共享库处于稳定状态。没有依赖被提交,因为两个智能体都不知道对方的存在。它们都变慢了,但都不报告原因。

共同点是:智能体产生阻塞的速度快于它们报告阻塞的速度。supervisor(你)需要自动浮现阻塞的工具,而不是等别人举旗的工具。

自动化依赖检测

解决方案是在创建任务时就产生明确的、可查询的依赖数据,并持续检查。以下是使用 beads 的实际操作。beads 是我们运行智能体集群所使用的 Git 原生 issue 追踪器。

智能体在创建任务时记录依赖:

# 智能体创建任务,发现需要一个不存在的 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 hook 或 5 分钟 cron 运行。当三个智能体独立创建了一个依赖循环时,你在几分钟内就能发现,而不是几小时后当三个都停滞时才知道。

一条命令浮现所有阻塞任务:

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

现在你知道智能体 3 和智能体 7 卡住了,卡在什么上,以及需要做什么来解除阻塞。整个查询在 10K issue 数据库上只需 30ms。

通过分支命名约定检测被阻塞的 PR:

#!/bin/bash
# blocked-prs.sh: find PRs whose dependencies haven't merged

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

二十行 shell。在本地运行,读取本地数据,告诉你智能体的哪些 PR 还不能合并以及原因。

CLI 优先的分类工作流

智能体工作流中的分类不是会议。它是一个循环运行的脚本。supervisor(人类或智能体)查看什么卡住了,对每个项目做出决策。

这是我们实际运行的分类脚本:

#!/bin/bash
# triage.sh: agentic fleet blocker triage

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

对人类团队来说,"停滞"意味着 48 小时没有更新。对智能体来说,一个 in-progress 任务 2 小时的沉默就是警示信号。要么智能体卡住了没汇报,要么它崩溃了。无论哪种情况,你都需要去看。

每个阻塞项的决策树:

  1. 其他智能体能解除阻塞吗? 提升阻塞任务的优先级,分配可用智能体。
  2. 依赖是虚假的吗? 智能体有时在规划阶段会声明过于保守的依赖。如果阻塞不是真实的,移除它:bd dep remove beads-x7q beads-m2k(移除 x7q 对 m2k 的依赖,立即解除 x7q 的阻塞)。
  3. 工作可以拆分吗? 让被阻塞的智能体做不需要依赖的部分。为剩余部分创建后续任务。
  4. 是外部阻塞吗? 只有人类能解决的事情(API key、设计决策、权限授予)。标记它,记录预期解决时间,把智能体重新分配到其他就绪工作。

选项 2 在智能体工作中经常发生。它们根据规划时对代码库的理解来建模依赖。一旦实现开始,工作的真实形态往往显示那些依赖中有一半是不必要的。

实时了解智能体工作

每 30 分钟运行一次分类脚本会留下空白。当智能体工作很快时,两次检查之间会发生很多事。问题变成:你能实时看到阻塞的形成吗?

Beadbox 是这样做的:

beads 数据库位于你文件系统的 .beads/ 目录中。智能体运行的每个 bd updatebd createbd close 都会写入该目录。Beadboxfs.watch() 监听它,并在毫秒内通过 WebSocket 推送变化到 UI。

实际效果:智能体 5 在终端运行 bd update beads-x7q --status=closed。Beadbox 面板立即显示该任务为已关闭,任何之前被它阻塞的任务都亮起为新可用。你看到级联效应,不需要运行任何命令。

这很重要,因为智能体工作会产生突发。一个智能体可能在 90 秒内关闭三个任务,每个解除不同下游工作的阻塞。30 秒刷新间隔的轮询式面板会显示令人困惑的中间状态。亚秒级传播让你在全貌发生时就看到它。

如果你不使用 Beadbox,文件系统监听仍然有效:

# Watch the beads database for changes, alert on new blocks
# Note: fswatch fires on every write. In production you'd debounce this
# (e.g., sleep 2 after each trigger) to avoid noise during burst writes.
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"
    # Pipe to ntfy, Slack webhook, or any notification system
  fi
done

用 CI 闭环:

# In your CI post-build step: auto-close the issue when the build passes
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 关闭那个 issue 时,所有被它阻塞的任务都解除阻塞。如果智能体在监听 bd ready 寻找新工作,它会自动认领已解除阻塞的任务。常规解除阻塞不需要人工介入。

这就是追踪状态的工具和传播状态的工具之间的区别。大多数项目管理软件做前者:你更新一张卡片,卡片变色。传播意味着下游效应(解除阻塞依赖者、浮现可用工作、更新进度汇总)自动发生,不需要任何人点击任何东西。

评估智能体工作流的分类工具

如果你在为管理智能体集群挑选工具,需求和人类团队不同。

必须有:智能体可调用的 CLI。 如果你的 issue 追踪器只有 Web UI,智能体用不了。它们需要能运行 shell 命令。bd createbd updatebd blocked 都是单行命令,任何编程智能体都知道怎么执行。REST API 也行,但需要 auth token、HTTP 客户端和错误处理。Unix 管道更简单。

必须有:可查询的依赖图。 "Blocked"作为状态标签对自动化没用。你需要将"A 依赖 B"作为结构化数据,这样脚本可以遍历图、检测循环、计算什么就绪了。

必须有:亚秒级本地读取。 当智能体查询可用工作时,响应时间很重要。每次查询 2 秒的 API 往返,乘以 10 个智能体每分钟轮询一次,会产生可测量的开销。beads 在 10K issue 数据库上返回 bd ready 结果只需 30ms,因为一切都在本地。

锦上添花:实时变更传播。 如果智能体每小时提交和解决 50 个 issue,你需要看到状态随变化而变,而不是等刷新间隔。

红旗:"AI 驱动的阻塞检测"。 声称通过分析 issue 描述来检测阻塞的工具会产生误报,并遗漏那些从未被写下来的真实阻塞。明确的 bd dep add 声明优于推断。

红旗:需要浏览器才能分类的工具。 通过 Web UI 解除一个任务的阻塞需要 5-15 秒的点击。通过 CLI,bd dep remove 只需 18ms。在 50 个阻塞任务上,那就是 1 分钟对 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 + auth tokens API + auth tokens gh CLI bd CLI (Unix 管道)

Supervisor 循环

这是我们每天管理 10+ AI 智能体在同一项目上的工作流:

  1. 智能体在创建任务时声明依赖。 每个有前置条件的 bd create 立即跟一个 bd dep add。每个任务只是一个额外的 CLI 调用。

  2. Supervisor 智能体每 30 分钟运行 bd blocked 如果有新的阻塞,它要么自己解决(重新排优先级、重新分配),要么通知人类。

  3. Beadbox 运行在人类的屏幕上。 面板实时显示完整的依赖图,高亮被阻塞的任务。大多数时候,自动化处理常规解除阻塞。当它无法处理时(外部依赖、架构决策、权限授予),人类立即看到问题并介入。

  4. 停滞任务被积极标记。 一个智能体如果 2 小时没有更新其 in-progress 任务,要么卡住了要么崩溃了。supervisor 检查并要么提醒智能体、重新分配工作,或进行调查。

  5. 虚假依赖被持续清理。 智能体在规划阶段过度声明依赖。随着实现揭示工作的真实形态,supervisor(或智能体自己)移除那些被证明不必要的依赖。干净的图才是有用的图。

底层原则:智能体速度快但不具备自我感知。它们不知道其他智能体在做什么,不注意阻塞何时解除,被卡住时也不抱怨。supervisor 的职责是成为连接所有这一切的神经系统。结构化的依赖数据,自动查询并可视化呈现,是让这一切成为可能的关键。


Beadbox 在 beta 期间免费。 它让你实时看到智能体在做什么、什么被阻塞、以及什么刚变为可用。

brew tap beadbox/cask && brew install --cask beadbox

如果你已经在使用 beads,Beadbox 直接读取你现有的 .beads/ 目录,无需导入步骤。试试看。