工作树多Agent并行:Git Worktree隔离 + Hermes Spawn 3个独立Agent
系列:06-多工具集成实战 标签:Git Worktree、Hermes Agent、多Agent并行、前端后端测试 日期:2026-05-22
简介
在真实的项目开发中,前端、后端、测试往往是三条并行推进的工作线。当这三个任务由不同的大模型 Agent 分别承担时,如何避免它们互相干扰、如何保证代码隔离、又如何最终汇聚到同一个 Git 仓库?这就是本文要解决的核心问题。
我们将使用 Git Worktree 实现物理级别的目录隔离,结合 Hermes Agent 的多实例 Spawn 能力,让 3 个独立的 Agent 同时在不同的工作树中推进各自的开发任务。
一、为什么需要 Git Worktree?
1.1 传统多分支开发的痛点
在没有 Worktree 的情况下,如果你需要同时开发三个功能分支,常见的做法是:
# 切换来切换去,极其痛苦
git checkout feat/frontend-ui # 做前端
git stash
git checkout feat/backend-api # 做后端
git stash
git checkout feat/test-suite # 做测试这种方式的致命问题:
- 上下文频繁切换:每次
checkout都要重新安装依赖、重启服务 - 无法并行:同一时刻只能在一个分支上工作
- IDE 混乱:编辑器索引不断刷新,自动补全失效
- Agent 冲突:多个 Agent 操作同一个目录会导致文件锁竞争
1.2 Git Worktree 的优雅方案
Git Worktree 允许你从同一个仓库中"克隆"出多个独立的工作目录,每个目录绑定不同的分支:
项目根目录/
├── .git/ # 共享的 .git 目录
├── main/ # 主工作树(main分支)
├── worktrees/
│ ├── frontend/ # feat/frontend-ui 分支
│ ├── backend/ # feat/backend-api 分支
│ └── test/ # feat/test-suite 分支关键优势:
- 共享 .git 对象:不需要克隆整个仓库,节省磁盘空间
- 物理隔离:每个 worktree 是完全独立的目录
- 并行操作:可以同时在三个目录中运行
git add、git commit、构建命令 - 自动同步:在一个 worktree 中的
push会在其他 worktree 中可见(通过git fetch)
二、实战:Hermes Spawn 3个独立 Agent
2.1 初始化项目与 Worktree
首先,创建一个完整的前后端分离项目,然后初始化 Worktree:
# 创建主项目
mkdir -p /opt/data/multi-agent-project && cd /opt/data/multi-agent-project
git init
git checkout -b main
# 创建项目骨架
mkdir -p frontend/src backend/src test/suites
# 初始化前端
cd frontend
npm init -y
npm install react react-dom typescript @types/react
cd ..
# 初始化后端
cd backend
npm init -y
npm install express @types/express
cd ..
# 提交初始结构
git add -A
git commit -m "feat: initialize project structure"接下来,创建三个 Worktree:
# 创建三个分支并绑定 worktree
git branch feat/frontend-ui
git branch feat/backend-api
git branch feat/test-suite
# 创建 worktree 目录
git worktree add ../worktrees/frontend feat/frontend-ui
git worktree add ../worktrees/backend feat/backend-api
git worktree add ../worktrees/test feat/test-suite
# 验证
git worktree list
# /opt/data/multi-agent-project main
# /opt/data/worktrees/frontend feat/frontend-ui
# /opt/data/worktrees/backend feat/backend-api
# /opt/data/worktrees/test feat/test-suite2.2 Hermes Agent 配置文件
我们使用 Hermes 的 spawn 能力,配置三个独立 Agent 实例。每个 Agent 拥有不同的 system prompt、工作目录和任务目标。
创建配置文件 hermes-agents-config.json:
{
"agents": [
{
"id": "frontend-agent",
"name": "Frontend Developer Agent",
"model": "claude-sonnet-4",
"workdir": "/opt/data/worktrees/frontend",
"system_prompt": "你是资深前端开发工程师,负责 React 组件开发。你的工作目录是 /opt/data/worktrees/frontend。请按照以下要求完成任务:\n1. 使用 React + TypeScript 编写组件\n2. 遵循 Clean Code 原则\n3. 每次完成后自动 git commit\n4. 遇到构建错误时自行调试修复",
"tasks": [
"创建 UserDashboard 组件",
"实现数据表格与分页功能",
"编写单元测试"
],
"allowed_commands": ["npm run build", "npm test", "git add", "git commit", "git push"]
},
{
"id": "backend-agent",
"name": "Backend Developer Agent",
"model": "claude-sonnet-4",
"workdir": "/opt/data/worktrees/backend",
"system_prompt": "你是资深后端开发工程师,负责 Node.js API 开发。你的工作目录是 /opt/data/worktrees/backend。请按照以下要求完成任务:\n1. 使用 Express + TypeScript 构建 RESTful API\n2. 遵循 REST 规范\n3. 每次完成后自动 git commit\n4. 编写完整的接口文档",
"tasks": [
"创建用户 CRUD API",
"实现 JWT 认证中间件",
"添加请求速率限制"
],
"allowed_commands": ["npm run build", "npm start", "npm test", "git add", "git commit", "git push"]
},
{
"id": "test-agent",
"name": "Test Engineer Agent",
"model": "claude-sonnet-4",
"workdir": "/opt/data/worktrees/test",
"system_prompt": "你是资深测试工程师,负责集成测试和 E2E 测试。你的工作目录是 /opt/data/worktrees/test。请按照以下要求完成任务:\n1. 使用 Jest + Supertest 编写 API 测试\n2. 使用 Playwright 编写 E2E 测试\n3. 每次完成后自动 git commit\n4. 发现 Bug 时创建 issue 描述",
"tasks": [
"编写 API 集成测试套件",
"创建 E2E 用户流程测试",
"生成测试覆盖率报告"
],
"allowed_commands": ["npm test", "npm run e2e", "git add", "git commit", "git push"]
}
],
"orchestration": {
"parallel": true,
"sync_interval": "30s",
"merge_strategy": "rebase",
"conflict_resolution": "manual"
}
}2.3 Hermes 编排脚本
编写一个编排脚本,让 Hermes 同时启动三个 Agent:
#!/bin/bash
# multi-agent-spawn.sh - Hermes 多Agent并行编排脚本
set -e
PROJECT_ROOT="/opt/data"
WORKTREE_ROOT="/opt/data/worktrees"
CONFIG="/opt/data/multi-agent-project/hermes-agents-config.json"
echo "=== 多Agent并行编排系统启动 ==="
echo "开始时间: $(date)"
# 阶段1: 同步所有 worktree 到最新状态
echo ""
echo "📡 阶段1: 同步所有工作树..."
cd "$PROJECT_ROOT/multi-agent-project"
git fetch origin
for wt in frontend backend test; do
cd "$WORKTREE_ROOT/$wt"
git pull origin feat/${wt}-ui 2>/dev/null || git pull origin feat/${wt}-api 2>/dev/null || git pull origin feat/test-suite 2>/dev/null || true
echo " ✅ $wt worktree 已同步"
done
# 阶段2: Spawn 三个独立 Agent
echo ""
echo "🚀 阶段2: 启动三个独立Agent..."
# Agent 1: 前端开发
hermes spawn \
--config "$CONFIG" \
--agent-id frontend-agent \
--session-id frontend-session-$(date +%s) \
--async \
--log "$PROJECT_ROOT/logs/frontend-agent.log" &
FRONTEND_PID=$!
echo " ✅ Frontend Agent 已启动 (PID: $FRONTEND_PID)"
# Agent 2: 后端开发
hermes spawn \
--config "$CONFIG" \
--agent-id backend-agent \
--session-id backend-session-$(date +%s) \
--async \
--log "$PROJECT_ROOT/logs/backend-agent.log" &
BACKEND_PID=$!
echo " ✅ Backend Agent 已启动 (PID: $BACKEND_PID)"
# Agent 3: 测试开发
hermes spawn \
--config "$CONFIG" \
--agent-id test-agent \
--session-id test-session-$(date +%s) \
--async \
--log "$PROJECT_ROOT/logs/test-agent.log" &
TEST_PID=$!
echo " ✅ Test Agent 已启动 (PID: $TEST_PID)"
echo ""
echo "⏳ 等待所有Agent完成..."
# 阶段3: 等待并收集结果
wait $FRONTEND_PID && echo " ✅ Frontend Agent 完成" || echo " ❌ Frontend Agent 失败"
wait $BACKEND_PID && echo " ✅ Backend Agent 完成" || echo " ❌ Backend Agent 失败"
wait $TEST_PID && echo " ✅ Test Agent 完成" || echo " ❌ Test Agent 失败"
# 阶段4: 汇总各 Agent 产出
echo ""
echo "📊 阶段4: 汇总产出..."
for wt in frontend backend test; do
cd "$WORKTREE_ROOT/$wt"
echo ""
echo "--- ${wt} 分支变更 ---"
git log --oneline -5
echo "文件变更:"
git diff --stat main..HEAD 2>/dev/null || echo " (无变更)"
done
echo ""
echo "=== 编排完成 ==="
echo "结束时间: $(date)"2.4 Agent 实际工作示例
让我们看看每个 Agent 在各自 worktree 中的实际工作内容。
Frontend Agent 的工作产出:
// worktrees/frontend/src/components/UserDashboard.tsx
import React, { useState, useEffect, useCallback } from 'react';
import { DataTable } from './DataTable';
import { Pagination } from './Pagination';
import { fetchUsers, User } from '../api/users';
interface UserDashboardProps {
pageSize?: number;
onUserSelect?: (user: User) => void;
}
export const UserDashboard: React.FC<UserDashboardProps> = ({
pageSize = 10,
onUserSelect,
}) => {
const [users, setUsers] = useState<User[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [totalPages, setTotalPages] = useState(1);
const loadData = useCallback(async (page: number) => {
try {
setLoading(true);
setError(null);
const response = await fetchUsers({ page, pageSize });
setUsers(response.data);
setTotalPages(response.pagination.totalPages);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}, [pageSize]);
useEffect(() => {
loadData(currentPage);
}, [currentPage, loadData]);
if (loading && users.length === 0) {
return <div className="dashboard-loading">Loading users...</div>;
}
if (error) {
return <div className="dashboard-error">Error: {error}</div>;
}
return (
<div className="user-dashboard">
<h2>User Management</h2>
<DataTable
data={users}
columns={['id', 'name', 'email', 'role', 'status']}
onRowClick={onUserSelect}
/>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</div>
);
};
export default UserDashboard;Backend Agent 的工作产出:
// worktrees/backend/src/routes/users.ts
import { Router, Request, Response } from 'express';
import { body, param, query } from 'express-validator';
import { validate } from '../middleware/validate';
import { authenticate } from '../middleware/authenticate';
import { rateLimit } from '../middleware/rateLimit';
import { UserService } from '../services/userService';
const router = Router();
const userService = new UserService();
// GET /api/users?page=1&pageSize=10
router.get(
'/',
authenticate,
rateLimit({ windowMs: 60000, max: 100 }),
query('page').optional().isInt({ min: 1 }),
query('pageSize').optional().isInt({ min: 1, max: 100 }),
validate,
async (req: Request, res: Response) => {
const page = parseInt(req.query.page as string) || 1;
const pageSize = parseInt(req.query.pageSize as string) || 10;
const { users, total } = await userService.findAll(page, pageSize);
res.json({
data: users,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize),
},
});
}
);
// POST /api/users
router.post(
'/',
authenticate,
rateLimit({ windowMs: 60000, max: 20 }),
body('name').isString().isLength({ min: 2, max: 50 }),
body('email').isEmail(),
body('role').isIn(['admin', 'user', 'moderator']),
validate,
async (req: Request, res: Response) => {
const user = await userService.create(req.body);
res.status(201).json({ data: user });
}
);
// PUT /api/users/:id
router.put(
'/:id',
authenticate,
param('id').isInt({ min: 1 }),
body('name').optional().isString().isLength({ min: 2, max: 50 }),
body('email').optional().isEmail(),
validate,
async (req: Request, res: Response) => {
const user = await userService.update(
parseInt(req.params.id),
req.body
);
res.json({ data: user });
}
);
// DELETE /api/users/:id
router.delete(
'/:id',
authenticate,
param('id').isInt({ min: 1 }),
validate,
async (req: Request, res: Response) => {
await userService.delete(parseInt(req.params.id));
res.status(204).send();
}
);
export default router;Test Agent 的工作产出:
// worktrees/test/suites/api/userApi.test.ts
import request from 'supertest';
import { app } from '../../../backend/src/app';
import { createTestUser, generateToken, cleanupDb } from '../helpers/testDb';
describe('User API Integration Tests', () => {
let authToken: string;
beforeEach(async () => {
await cleanupDb();
const adminUser = await createTestUser({ role: 'admin' });
authToken = generateToken(adminUser);
});
describe('GET /api/users', () => {
it('should return paginated users for authenticated admin', async () => {
// 创建测试数据
for (let i = 0; i < 25; i++) {
await createTestUser({ name: `User ${i}` });
}
const response = await request(app)
.get('/api/users')
.query({ page: 1, pageSize: 10 })
.set('Authorization', `Bearer ${authToken}`);
expect(response.status).toBe(200);
expect(response.body.data).toHaveLength(10);
expect(response.body.pagination).toEqual({
page: 1,
pageSize: 10,
total: 25,
totalPages: 3,
});
});
it('should reject unauthenticated requests', async () => {
const response = await request(app).get('/api/users');
expect(response.status).toBe(401);
expect(response.body.error).toBe('Authentication required');
});
it('should respect rate limiting', async () => {
const requests = Array(101).fill(null).map(() =>
request(app)
.get('/api/users')
.set('Authorization', `Bearer ${authToken}`)
);
const responses = await Promise.all(requests);
const rejected = responses.filter(r => r.status === 429);
expect(rejected.length).toBeGreaterThan(0);
});
});
describe('POST /api/users', () => {
it('should create a new user with valid data', async () => {
const response = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${authToken}`)
.send({
name: 'New User',
email: 'newuser@example.com',
role: 'user',
});
expect(response.status).toBe(201);
expect(response.body.data).toMatchObject({
name: 'New User',
email: 'newuser@example.com',
role: 'user',
});
});
it('should reject invalid email', async () => {
const response = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${authToken}`)
.send({
name: 'Bad User',
email: 'not-an-email',
role: 'user',
});
expect(response.status).toBe(400);
expect(response.body.errors).toContainEqual(
expect.objectContaining({ field: 'email' })
);
});
});
});三、合并与集成
3.1 各 Agent 自动提交
每个 Agent 在完成自己的任务后,会自动在各自的 worktree 中提交:
# Frontend Agent 提交
cd /opt/data/worktrees/frontend
git add -A
git commit -m "feat(frontend): implement UserDashboard with pagination
- Created UserDashboard component with React + TypeScript
- Added DataTable and Pagination sub-components
- Implemented error handling and loading states
- Integrated with users API"
# Backend Agent 提交
cd /opt/data/worktrees/backend
git add -A
git commit -m "feat(backend): implement user CRUD API with auth & rate limiting
- Created RESTful endpoints for user management
- Added JWT authentication middleware
- Implemented rate limiting per endpoint
- Added request validation with express-validator"
# Test Agent 提交
cd /opt/data/worktrees/test
git add -A
git commit -m "feat(test): add comprehensive API integration tests
- Created user API test suite with supertest
- Added pagination, auth, and rate limit tests
- Implemented test database helpers
- Added E2E test skeletons for user workflows"3.2 集成到主分支
当三个 Agent 都完成各自任务后,我们在主仓库中将三个分支合并:
cd /opt/data/multi-agent-project
# 拉取所有 worktree 的变更
git fetch --all
# 合并前端分支
git merge feat/frontend-ui --no-ff -m "Merge frontend: UserDashboard component"
# 合并后端分支
git merge feat/backend-api --no-ff -m "Merge backend: user CRUD API"
# 合并测试分支
git merge feat/test-suite --no-ff -m "Merge test: API integration tests"
# 解决可能的冲突
# 如果 frontend 和 backend 都修改了 src/types/index.ts:
# 手动编辑后:
git add src/types/index.ts
git commit -m "chore: resolve merge conflict in types"
# 推送到远程
git push origin main3.3 清理 Worktree
项目合并后,可以清理不再需要的 worktree:
# 删除 worktree(同时删除对应分支)
git worktree remove ../worktrees/frontend
git worktree remove ../worktrees/backend
git worktree remove ../worktrees/test
# 或者保留 worktree 目录但删除 git 关联
git worktree prune
# 查看当前 worktree 状态
git worktree list四、架构优势分析
4.1 并行度对比
| 方案 | 并行度 | 隔离性 | 资源消耗 |
|---|---|---|---|
| 串行单 Agent | 1x | N/A | 最低 |
| 多分支 checkout 切换 | 1x(伪并行) | 差 | 低 |
| 完整仓库克隆 | 3x | 高 | 最高 |
| Git Worktree + Hermes | 3x | 高 | 中等 |
4.2 资源消耗分析
完整克隆方案:
仓库大小: 500MB × 3 = 1500MB
安装依赖: node_modules × 3 = 600MB
总计: ~2100MB
Worktree 方案:
共享 .git: 500MB
工作目录: 50MB × 3 = 150MB
共享依赖: node_modules (symlink) = 200MB
总计: ~850MB
节省: ~60% 磁盘空间4.3 错误隔离
每个 Agent 在自己的 worktree 中操作,即使某个 Agent 出现以下问题也不会影响其他 Agent:
- 构建失败:不会阻塞其他分支的构建
- 依赖冲突:worktree 间的 node_modules 可以独立配置
- Git 冲突:冲突只在 merge 阶段出现,不影响并行开发
- Agent 崩溃:单个 Agent 失败不影响其他 Agent 运行
五、最佳实践
5.1 Worktree 命名规范
# 推荐格式:<功能域>/<任务描述>
git worktree add ../worktrees/feat-auth feat/auth-system
git worktree add ../worktrees/fix-payment fix/payment-bug
git worktree add ../worktrees/perf-dashboard perf/optimize-dashboard
# 避免过长的名称
git worktree add ../worktrees/feat feat/very-long-feature-name5.2 Agent 通信机制
虽然三个 Agent 在不同的 worktree 中工作,但它们可能需要共享信息。推荐做法:
# 方式1:通过 Git 提交消息传递信息
git commit -m "feat(backend): add user API
NOTE: @frontend-agent User endpoint is ready at /api/users
Response format: { data: User[], pagination: {...} }
Auth header required: Authorization: Bearer <token>"
# 方式2:通过共享文件传递契约
# 创建 contracts/api-contract.json
# 所有 Agent 读取这个文件了解 API 规范5.3 同步策略
#!/bin/bash
# sync-worktrees.sh - 定期同步脚本
WORKTREE_ROOT="/opt/data/worktrees"
INTERVAL=300 # 5分钟
while true; do
echo "[$(date)] 同步所有 worktree..."
for wt in "$WORKTREE_ROOT"/*/; do
if [ -d "$wt/.git" ]; then
cd "$wt"
git fetch origin
BRANCH=$(git branch --show-current)
git rebase origin/$BRANCH 2>/dev/null || true
echo " ✅ $(basename $wt) 已同步"
fi
done
sleep $INTERVAL
done配图说明
| 图片 | 说明 |
|---|---|
architecture.png |
架构图:Hermes Spawn 三个 Agent,分别对应 frontend/backend/test 三个 worktree,最终 merge 到 main 分支 |
worktree-diagram.png |
Git Worktree 结构图:共享 .git 对象,独立工作目录的可视化 |
timeline.png |
时间线图:三个 Agent 并行工作的时间轴,展示并行度提升 |
总结
本文详细演示了如何使用 Git Worktree + Hermes Agent 实现真正的前后端测试三端并行开发。核心要点:
- Git Worktree 提供了物理隔离且共享 .git 对象的轻量级多分支方案,比完整克隆节省约 60% 磁盘空间
- Hermes Spawn 可以为每个 worktree 配置独立的 Agent 实例,拥有不同的 system prompt、任务目标和权限控制
- 并行开发 的效率提升是线性的——3 个 Agent 同时工作,理论上可以达到 3 倍的开发速度
- 自动提交与合并 确保了代码变更可以有序地集成到主分支
这套方案特别适合:
- 前后端分离项目的并行开发
- 大型功能模块的拆分开发
- 测试团队与开发团队的并行工作
- Code Review 前的预发分支验证
下篇预告
CICD多Agent流水线
在下一篇中,我们将把这套并行开发方案与 CI/CD 流水线深度集成。你将看到:
- 🔄 完整的 构建→审查→测试→部署 四阶段流水线
- 🔍 Codex 负责 PR 代码审查,自动检测代码质量和规范
- 🔧 Claude Code 根据审查结果自动修复问题
- 📢 Hermes 负责最终部署和多渠道通知
- 如何将三个 Agent 无缝串联到 GitHub Actions / GitLab CI 中
敬请期待!
本文属于「AI Agent 实战」系列。如果你对本系列感兴趣,欢迎订阅或分享给你的团队。