想象这样一个场景:你同时让三个助手并行工作,一个查资料、一个写报告、一个做分析。但现实很快会让你头疼------三个人可能同时抢着用同一台打印机,或者各自闷头干活却不知道对方在做什么,又或者你刚布置完任务想追加需求,却发现已经没人理你了。
这恰恰是多 Agent 系统面临的真实困境。
JiuwenSwarm 基于 openJiuwen 框架构建了一套四层协调体系,让"多 Agent 协作"从理想变成现实。解决的是四个核心问题:资源竞争怎么控制、跨进程通信怎么实现、成员状态怎么实时同步、技能怎么高效复用。
一、为什么需要 Coordination
单 Agent 处理任务逻辑简单,但并行能力有限、无法处理复合任务、上下文容易膨胀。多 Agent 的价值在于并行,但并行带来了四个实实在在的难题:
资源竞争。多个 Agent 同时调用 LLM API,超出服务端限制时触发 429 错误。没有协调机制,整个团队会在这一刻集体失败。
通信无序。成员之间需要传递消息、协调任务、共享状态。没有统一的通信秩序,团队会陷入死锁或消息丢失。
状态黑箱。每个成员有自己的运行时状态,Leader 需要知道谁在做什么、任务进展到哪一步。没有观测机制,团队就变成了黑箱。
能力重复。每个新成员从零配置技能,响应速度大打折扣。好的协调机制应该让能力复用无缝发生。
JiuwenSwarm 的做法是把"协调"本身当成一个独立的问题来处理,而不是当作 Agent 的附属功能。以下是这套体系的核心组件和源码结构:
运行环境
|----------|------------------------------------------------------------------|
| 项目 | 配置值 |
| 操作系统 | Windows 10 / macOS / Linux |
| Python | 3.11 / 3.12 / 3.13 |
| 模型服务 | 华为云 MaaS / OpenAI 兼容接口 / ModelScope 等 |
| 通信渠道 | Web / 飞书 / 钉钉 / 企业微信 / 小艺 / Telegram / Discord / WhatsApp / 个人微信 |
| Agent 框架 | openJiuwen(内置) |
源码结构
Coordination 相关代码集中在 jiuwenclaw/agentserver/team/ 目录下:
jiuwenclaw/agentserver/team/
├── llm_limiter.py # LLM 并发限流器
├── distributed_runtime.py # 分布式运行时协调
├── event_types.py # 12 种事件类型定义
├── monitor_handler.py # 实时事件监控处理器
├── team_runtime_inheritance.py # 技能继承与 Rail 构建
├── team_manager.py # 团队生命周期管理器
└── config_loader.py # 团队配置加载器
核心组件分工
|------------------------|--------------------|--------|
| 组件 | 职责 | 对应协调层 |
| LLMLimiter | LLM 并发限流与 429 重试 | 并发协调 |
| DistributedRuntime | 分布式模式判断、地址解析、存储协调 | 分布式协调 |
| TeamMonitorHandler | 实时事件监控、事件流推送 | 事件协调 |
| EventTypes | 12 种事件类型定义与 SDK 映射 | 事件协调 |
| TeamRuntimeInheritance | 技能继承、能力卡过滤、Rail 构建 | 能力协调 |
| TeamManager | 团队生命周期管理(整合各层协调) | 生命周期协调 |
二、四层协调详解

2.1 并发协调:LLM Limiter
三个 Agent 同时调用 LLM 是常见的场景,但 API 服务商不会让你无限制地调用。超过并发限制时会收到 429 错误,如果没有任何协调机制,整个团队都会在这一刻卡住。
llm_limiter.py 用一个很巧妙的方式解决了这个问题------它在运行时替换了 OpenAI SDK 的方法,加入了信号量和重试逻辑:
_semaphore: Optional[asyncio.Semaphore] = None
_MAX_RETRIES = 5
_BASE_DELAY = 2.0
def install_llm_limiter(max_concurrency: int = 2) -> None:
global _semaphore, _original_create, _installed
_semaphore = asyncio.Semaphore(max_concurrency)
from openai.resources.chat.completions import AsyncCompletions
_original_create = AsyncCompletions.create
@wraps(_original_create)
async def _limited_create(self, *args, **kwargs):
async with _semaphore:
for attempt in range(_MAX_RETRIES):
try:
return await _original_create(self, *args, **kwargs)
except Exception as e:
is_429 = "429" in str(e) or "rate" in str(e).lower()
if not is_429 or attempt == _MAX_RETRIES - 1:
raise
delay = _BASE_DELAY * (2 ** attempt)
await asyncio.sleep(delay)
AsyncCompletions.create = _limited_create
信号量确保最多只有 2 个请求同时发出,其他的排队等着。指数退避意味着第一次失败等 2 秒,第二次等 4 秒,第三次等 8 秒------既不会疯狂重试给服务器压力,也不会一遇挫就放弃。patch 只发生一次,但并发数可以随时调整。
2.2 分布式协调:Distributed Runtime
开发环境下,Leader 和 Teammate 跑在同一个进程里,通信成本几乎为零。但到了生产环境,特别是需要利用多台 GPU 服务器的时候,跨进程甚至跨机器的通信就成了刚需。
Team Agent 支持两种模式:
|---------------|----------------------------|----------------|
| 模式 | 说明 | 适用场景 |
| inprocess(默认) | Leader 和 Teammate 在同一进程内通信 | 开发调试、小规模任务 |
| pyzmq(分布式) | 成员通过 PyZMQ 跨进程/跨节点通信 | 生产环境、多 GPU 服务器 |
模式判断逻辑很直接,看配置里有没有指定 distributed 或者 pyzmq:
def is_distributed_mode(config_base: dict[str, Any]) -> bool:
team_cfg = config_base.get("team", {})
runtime_cfg = team_cfg.get("runtime", {})
runtime_mode = str(runtime_cfg.get("mode", "")).strip().lower()
if runtime_mode == "distributed":
return True
transport_cfg = team_cfg.get("transport", {})
transport_type = str(transport_cfg.get("type", "")).strip().lower()
return transport_type == "pyzmq"
在分布式模式下,每个节点必须知道自己扮演什么角色(Leader 还是 Teammate),以及如何找到其他成员。地址配置大概长这样:
team:
transport:
type: "pyzmq"
params:
leader:
host: "192.168.1.10"
direct_port: 18555
pub_port: 18556
sub_port: 18557
teammate:
host: "192.168.1.20"
direct_port: 18600
但实际使用时,配置往往是不完整的------可能只配了角色和主机名,端口号让系统自己推断。normalize_distributed_transport_fields() 就负责做这个补全工作:
if role == "leader":
local_direct_addr = f"tcp://0.0.0.0:{leader_direct_port}"
known_peers = [
{
"agent_id": local_member_name,
"addrs": [f"tcp://{teammate_host}:{teammate_direct_port}"],
}
]
pubsub_bind = True
else: # teammate
local_direct_addr = f"tcp://0.0.0.0:{teammate_direct_port}"
known_peers = [
{
"agent_id": leader_member_name,
"addrs": [f"tcp://{leader_host}:{leader_direct_port}"],
}
]
pubsub_bind = False
params["direct_addr"] = local_direct_addr
params["pubsub_publish_addr"] = f"tcp://{leader_host}:{leader_pub_port}"
params["pubsub_subscribe_addr"] = f"tcp://{leader_host}:{leader_sub_port}"
params["known_peers"] = known_peers
Leader 绑定端口等待连接,Teammate 主动找上门------这就是服务发现的本质。
分布式模式下,数据存储也从 SQLite 升级到 PostgreSQL。如果 Leader 启动时发现数据库还没准备好,会自动尝试启动本地集群,不需要人工干预。
2.3 事件协调:Monitor 与 Event Types
多 Agent 团队跑起来之后,你肯定想知道:谁在做什么?任务进展到哪一步了?成员之间有没有在通信?
JiuwenSwarm 定义了 12 种事件来覆盖这些观测需求:
成员事件(team.member):
|--------------------------|----------|-----------------------------------|
| 事件 | 触发时机 | 关键字段 |
| MEMBER_SPAWNED | 新成员被创建 | member_id, timestamp |
| MEMBER_STATUS_CHANGED | 成员状态变更 | member_id, old_status, new_status |
| MEMBER_EXECUTION_CHANGED | 成员执行状态变更 | member_id, old_status, new_status |
| MEMBER_RESTARTED | 成员被重启 | member_id, reason, restart_count |
| MEMBER_SHUTDOWN | 成员被关闭 | member_id, force |
任务事件(team.task):
|----------------|-----------|-----------------|
| 事件 | 触发时机 | 关键字段 |
| TASK_CREATED | 新任务被创建 | task_id, status |
| TASK_CLAIMED | 任务被某个成员认领 | task_id |
| TASK_COMPLETED | 任务完成 | task_id |
| TASK_CANCELLED | 任务被取消 | task_id |
| TASK_UNBLOCKED | 任务解除阻塞 | task_id |
消息事件(team.message):
|-------------------|----------|---------------------------------------------|
| 事件 | 触发时机 | 关键字段 |
| MESSAGE_P2P | 成员间点对点消息 | message_id, from_member, to_member, content |
| MESSAGE_BROADCAST | 广播消息 | message_id, from_member, content |
这些事件是从 openjiuwen SDK 的 MonitorEventType 映射过来的:
SDK_TO_TEAM_EVENT_MAP: dict[MonitorEventType, TeamEventType] = {
MonitorEventType.MEMBER_SPAWNED: TeamEventType.MEMBER_SPAWNED,
MonitorEventType.MEMBER_STATUS_CHANGED: TeamEventType.MEMBER_STATUS_CHANGED,
MonitorEventType.TASK_CREATED: TeamEventType.TASK_CREATED,
MonitorEventType.TASK_CLAIMED: TeamEventType.TASK_CLAIMED,
MonitorEventType.TASK_COMPLETED: TeamEventType.TASK_COMPLETED,
MonitorEventType.MESSAGE: TeamEventType.MESSAGE_P2P,
MonitorEventType.BROADCAST: TeamEventType.MESSAGE_BROADCAST,
# ... 其他映射
}
TeamMonitorHandler 是事件的处理中枢,它封装了 openjiuwen 的 TeamMonitor,把底层的 SDK 事件转换成前端可以直接用的格式:
class TeamMonitorHandler:
def __init__(self, team_agent: TeamAgent, session_id: str):
self._team_agent = team_agent
self._session_id = session_id
self._monitor: TeamMonitor | None = None
self._event_queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue()
self._event_task: asyncio.Task | None = None
self._running = False
async def start(self) -> None:
self._monitor = create_monitor(self._team_agent)
await self._monitor.start()
self._running = True
self._event_task = asyncio.create_task(self._collect_events())
async def _collect_events(self) -> None:
async for event in self._monitor.events():
event_dict = await self._convert_event_to_dict(event)
if event_dict:
await self._event_queue.put(event_dict)
每种事件类型都有专门的 handler 来提取自己关心的字段:
@staticmethod
def _handle_member_spawned(base: dict[str, Any], event: MonitorEvent) -> dict[str, Any]:
base["member_id"] = event.member_id
base["status"] = "running"
base["timestamp"] = event.timestamp or ""
return base
@staticmethod
def _handle_task_claimed(base: dict[str, Any], event: MonitorEvent) -> dict[str, Any]:
base["task_id"] = event.task_id
return base
async def _handle_message(self, base: dict[str, Any], event: MonitorEvent) -> dict[str, Any]:
message_content = await self._get_message_content(event.message_id)
base.update({
"message_id": event.message_id,
"from_member": event.from_member,
"to_member": event.to_member,
"content": message_content,
})
return base
前端通过 events() 方法订阅事件流,超时机制确保即使没有新事件,循环也能及时退出检查运行状态。
2.4 能力协调:技能继承与 Rail 构建
每次创建新成员都要手动配置一遍技能?这显然太蠢了。更好的做法是让成员自动继承主 Agent 的能力,上岗就能干活。
技能继承分两步走。

第一步是全局技能同步到团队共享目录,这步在团队创建时只执行一次:
@staticmethod
def _copy_global_skills_to_team_shared_dir(spec: TeamAgentSpec) -> None:
global_skills_dir = get_agent_skills_dir()
ws_path = str(team_home(spec.team_name) / "team-workspace")
team_shared_skills_dir = Path(ws_path) / "skills"
copied_marker = team_shared_skills_dir / ".team_skills_copied"
if copied_marker.exists():
return
shutil.copytree(global_skills_dir, team_shared_skills_dir, dirs_exist_ok=True)
copied_marker.write_text("")
第二步是成员加入时,复制自己需要的技能到个人目录:
def copy_member_configured_skills(
member_skills_dir: Path,
selected_skills: list[str],
) -> None:
selected_skill_set = set(selected_skills)
member_skills_dir.mkdir(parents=True, exist_ok=True)
for skill_dir in global_skills_dir.iterdir():
if not (skill_dir / "SKILL.md").is_file():
continue
if skill_dir.name not in selected_skill_set:
continue
dest = member_skills_dir / skill_dir.name
if not dest.exists():
shutil.copytree(skill_dir, dest)
如果成员没有指定具体要哪些技能,就继承全部。
工具能力卡也是同样的逻辑,主 Agent 的可继承工具会自动加到新成员身上:
TOOL_WHITELIST = frozenset({
"free_search", "fetch_webpage", "paid_search", "vision",
"audio", "image_ocr", "visual_question_answering",
"generate_image", "audio_transcription",
})
def filter_inheritable_ability_cards(main_agent: Any) -> list[ToolCard]:
result = []
for ability in main_agent.ability_manager.list():
if isinstance(ability, ToolCard):
if ability.name in TOOL_WHITELIST:
result.append(ability)
return result
Rail 是 openjiuwen 的钩子机制,不同角色需要加载不同的 Rail。build_member_rails() 根据角色决定加载哪些:
RAIL_WHITELIST = frozenset({
"RuntimePromptRail",
"ResponsePromptRail",
"JiuClawStreamEventRail",
"TaskPlanningRail",
"SecurityRail",
"HeartbeatRail",
"AvatarPromptRail",
"FileSystemRail",
"TeamSkillRail",
"TeamSkillCreateRail",
"SkillEvolutionRail",
})
# Leader 专用:团队技能演进
if role == "leader" and team_ws_skills_dir:
team_skill_rail = TeamSkillRail(...)
rails_list.append(team_skill_rail)
# 非 Leader 专用:成员技能自演进
if role != "leader" and skills_dir:
evo_rail = build_skill_evolution_rail(...)
if evo_rail is not None:
rails_list.append(evo_rail)
2.5 生命周期协调:TeamManager
上面四层协调机制最终都要由 TeamManager 来统筹。它管理着团队实例、监控器、流式任务,还负责按正确的顺序释放资源:
class TeamManager:
def __init__(self):
self._team_agents: dict[str, TeamAgent] = {}
self._team_monitors: dict[str, TeamMonitorHandler] = {}
self._stream_tasks: dict[str, asyncio.Task] = {}
self._lock = asyncio.Lock()
self._team_skill_rails: dict[str, Any] = {}
self._team_skill_sync_targets: dict[str, tuple[Path, Path]] = {}
self._team_evolution_watchers: dict[str, asyncio.Task] = {}
一个设计细节值得注意:不同 Channel(飞书、Web、钉钉等)拥有独立的 TeamManager 实例,通过全局注册表管理:
_team_managers: dict[str, TeamManager] = {}
def get_team_manager(channel_id: str | None = None) -> TeamManager:
resolved_channel_id = str(channel_id or "default").strip() or "default"
manager = _team_managers.get(resolved_channel_id)
if manager is None:
manager = TeamManager()
_team_managers[resolved_channel_id] = manager
return manager
这样即使多个 Channel 同时使用 Team 模式,也不会互相干扰。
get_or_create_team() 实现了懒加载复用------已经有团队就直接用,没有才创建。创建之前还会清理掉同一 Channel 下其他会话的残留实例。
资源释放的顺序很关键,先取消任务、再停监控、最后销毁团队,任何一步出错都有兜底:
async def _destroy_team(self, session_id: str) -> bool:
# 1. 取消演进监控任务
watcher_task = self._team_evolution_watchers.pop(session_id, None)
if watcher_task and not watcher_task.done():
watcher_task.cancel()
# 2. 取消流式任务
stream_task = self._stream_tasks.pop(session_id, None)
if stream_task and not stream_task.done():
stream_task.cancel()
# 3. 停止 Monitor
monitor_handler = self._team_monitors.pop(session_id, None)
if monitor_handler is not None:
await monitor_handler.stop()
# 4. 清理状态
self._team_skill_rails.pop(session_id, None)
self._team_skill_sync_targets.pop(session_id, None)
# 5. 销毁团队
team_agent = self._team_agents.pop(session_id, None)
try:
cleaned = await team_agent.destroy_team(force=True)
finally:
await release_a2x_reservations_for_team(team_agent)
await _stop_team_messager(team_agent, session_id=session_id)
return cleaned
三、配置与部署
团队配置
在 ~/.jiuwenclaw/config/config.yaml 中添加 team 配置段即可启用:
team:
team_name: "my_team"
lifecycle: "persistent"
teammate_mode: "build_mode"
spawn_mode: "inprocess"
分布式模式需要更多配置:
team:
team_name: "distributed_team"
lifecycle: "persistent"
teammate_mode: "build_mode"
runtime:
mode: "distributed"
role: "leader"
transport:
type: "pyzmq"
params:
leader:
host: "192.168.1.10"
direct_port: 18555
pub_port: 18556
sub_port: 18557
teammate:
host: "192.168.1.20"
direct_port: 18600
storage:
type: "postgresql"
params:
connection_string: "postgresql+asyncpg://user:pass@127.0.0.1:5432/team_db"
模型配置
Team Agent 的模型配置继承自主 Agent:
models:
default:
model_client_config:
model_name: "qwen-plus"
client_provider: "openai"
model_config_obj:
model: "qwen-plus"
temperature: 0.7
启动服务
pip install jiuwenclaw
jiuwenclaw-init # 首次初始化
jiuwenclaw-start # 启动服务
四、实操演示:Coordination 在行动
下面我们用一个完整场景,完整还原四层协调机制在一次团队任务中的运作细节。为了便于理解,每个步骤都标注了协调机制的介入点。
4.1 场景设定
用户让 Team Agent 并行调研三个方向,每个方向独立生成报告:
"帮我全面调研一下 AI Agent 的最新进展,我需要了解三个方向:1)主流 Agent 框架的设计对比;2)Agent 通信协议的现状;3)Agent 在企业场景中的落地案例。每个方向生成一份独立的 Markdown 报告。"
三个方向互不依赖,天然适合并行处理。这个场景能完整展现四层协调的作用。
4.2 环境准备
第一步:启动服务
pip install jiuwenclaw
jiuwenclaw-init # 首次初始化,生成配置文件
jiuwenclaw-start # 启动服务
启动后,服务会:
- 加载
~/.jiuwenclaw/config/config.yaml中的配置 - 根据配置初始化 DeepAgent(主 Agent 实例)
- 挂载各类 Rails(钩子链)
- 启动 WebSocket Gateway,等待前端连接
协调机制在此步骤的体现 :配置加载时,如果 team 配置段存在,系统会调用 config_loader.py 的 load_team_spec_dict() 预加载团队配置,识别是否需要分布式模式。
第二步:打开前端,切换到集群模式
浏览器打开 http://localhost:5173,看到主界面后:
- 在输入框底部找到模式切换按钮
- 点击 "👥 集群模式" 按钮
|----|------------|-----------------|
| 按钮 | 模式 | 适用场景 |
| 📋 | agent.plan | 规划模式,单任务规划 |
| ⚙️ | agent.fast | 智能执行,快速响应 |
| 👥 | team | 集群模式,多 Agent 协作 |
点击后,按钮会高亮显示,表示已进入 Team 模式。此时 TeamManager 还没有创建 TeamAgent 实例,真正的创建发生在下一步------当你发送第一条消息时。
4.3 首次消息:团队创建与初始化
第三步:发送第一条任务消息
在输入框中粘贴以下内容:
帮我全面调研一下 AI Agent 的最新进展,我需要了解三个方向:
1)主流 Agent 框架的设计对比(LangGraph vs CrewAI vs AutoGen)
2)Agent 通信协议的现状(MCP、A2A、ACP)
3)Agent 在企业场景中的落地案例
每个方向生成一份独立的 Markdown 报告,保存到工作目录。
点击发送。这一步会发生一系列复杂的事情。

步骤分解:消息到达后的完整流程
4.3.1 is_team_mode 识别(协调机制无介入)
JiuwenSwarm.process_message_stream() 接收到消息后,首先根据 team flag 判断是普通 Agent 模式还是 Team 模式。这是纯业务逻辑判断,协调机制此时尚未介入。
4.3.2 TeamManager 获取或创建团队(生命周期协调)
# interface.py 核心逻辑
team_manager = get_team_manager(request.channel_id)
is_team_first_request = not team_manager.has_stream_task(session_id)
get_team_manager() 是 team_manager.py 中定义的工厂函数:
# team_manager.py
_team_managers: dict[str, TeamManager] = {} # 全局注册表
def get_team_manager(channel_id: str | None = None) -> TeamManager:
resolved_channel_id = str(channel_id or "default").strip() or "default"
manager = _team_managers.get(resolved_channel_id)
if manager is None:
manager = TeamManager()
_team_managers[resolved_channel_id] = manager
return manager
设计意图:不同 Channel(飞书、Web、钉钉等)拥有独立的 TeamManager 实例,互相不干扰。即使飞书和钉钉同时使用 Team 模式,它们的团队实例也是隔离的。
4.3.3 create_team:团队实例创建(分布式协调 + 能力协调)
如果是首次请求,get_or_create_team() 会触发 create_team(),完整流程如下:
第一步: PostgreSQL 检查(仅 分布式 模式)
# team_manager.py create_team() 片段
async def _ensure_postgresql_for_leader(config_base: dict[str, Any]) -> None:
# 检查 PostgreSQL 是否可用
if await is_pg_available(host, port):
return
# 如果不可用,尝试自动启动本地 PostgreSQL 集群
await ensure_postgresql_for_leader(...)
这一步只在 runtime.mode == distributed 时执行。进程内模式直接跳到下一步。
第二步:加载 TeamSpec
spec = self._load_team_spec(session_id)
config_loader.py 的 load_team_spec_dict() 负责从配置文件中读取并合并团队配置,生成一个完整的 TeamAgentSpec 字典,包含:
team_name:团队名称lifecycle:生命周期(persistent或temporary)teammate_mode:build_mode或runtime_modespawn_mode:inprocess或distributedleader:Leader 的成员配置agents:成员的工作区、迭代次数等配置
第三步:构建 Agent Customizer(能力协调)
spec.agent_customizer = self.build_agent_customizer(
spec, deep_agent, session_id,
request_id=request_id,
channel_id=channel_id,
request_metadata=request_metadata,
)
这里调用 team_runtime_inheritance.py 中的逻辑,为 Leader 和 Teammate 注入不同的 Rails:
# team_runtime_inheritance.py build_member_rails() 片段
RAIL_WHITELIST = frozenset({
"RuntimePromptRail", "ResponsePromptRail", "JiuClawStreamEventRail",
"TaskPlanningRail", "SecurityRail", "HeartbeatRail", "AvatarPromptRail",
"FileSystemRail", "TeamSkillRail", "TeamSkillCreateRail", "SkillEvolutionRail",
})
# Leader 专用 Rail:TeamSkillRail + TeamSkillCreateRail
if role == "leader" and team_ws_skills_dir:
team_skill_rail = TeamSkillRail(...)
rails_list.append(team_skill_rail)
# Teammate 专用 Rail:SkillEvolutionRail(成员技能自演进)
if role != "leader" and skills_dir:
evo_rail = build_skill_evolution_rail(...)
rails_list.append(evo_rail)
Rail 的职责(来自 openJiuwen 框架):
RuntimePromptRail:在 Agent 运行前注入运行时上下文ResponsePromptRail:在生成响应前对 Prompt 进行后处理SecurityRail:内容安全检查HeartbeatRail:心跳检测,维持成员存活状态TeamSkillRail:Leader 专用,管理团队技能演进SkillEvolutionRail:Teammate 专用,监听并应用技能自演进
第四步:全局技能同步到团队共享目录(能力协调)
# team_manager.py _copy_global_skills_to_team_shared_dir() 片段
@staticmethod
def _copy_global_skills_to_team_shared_dir(spec: TeamAgentSpec) -> None:
global_skills_dir = get_agent_skills_dir() # ~/.jiuwenclaw/skills/
ws_path = str(team_home(spec.team_name) / "team-workspace")
team_shared_skills_dir = Path(ws_path) / "skills"
copied_marker = team_shared_skills_dir / ".team_skills_copied"
if copied_marker.exists():
return # 已经同步过,跳过
shutil.copytree(global_skills_dir, team_shared_skills_dir, dirs_exist_ok=True)
copied_marker.write_text("")
这个方法确保全局技能只复制一次 到团队共享目录(team-workspace/skills/),所有成员后续从这里继承。copied_marker 文件防止重复复制。
第五步: 分布式 模式挂载引导 监听器 (分布式协调)
如果配置为 distributed 模式,还需要挂载远程引导相关的监听器:
if self._is_distributed_mode(config_base):
attach_distributed_local_spawn_guard(...)
attach_spawn_member_remote_bootstrap_wrapper(...)
attach_remote_bootstrap_ack_listener(...)
attach_remote_teammate_bootstrap_listener(...)
这些监听器负责在分布式环境下,Leader 能正确发现和引导远程 Teammate 加入团队。
第六步:调用 core API 创建 TeamAgent
team_agent = spec.build() # 调用 openJiuwen core TeamAgentSpec.build()
self._team_agents[session_id] = team_agent
build() 是 openJiuwen core 的 API,它会根据 TeamAgentSpec 创建真正的 TeamAgent 实例。
4.3.4 启动 Monitor(事件协调)
# team_helpers.py process_team_message_stream() 片段
monitor_handler = TeamMonitorHandler(team_agent, session_id)
await monitor_handler.start()
team_manager.register_monitor(session_id, monitor_handler)
if monitor_handler.is_running:
asyncio.create_task(
_consume_monitor_events(channel_id, session_id, monitor_handler)
)
TeamMonitorHandler 封装了 openJiuwen 的 TeamMonitor,它的 start() 方法:
# monitor_handler.py start() 片段
async def start(self) -> None:
self._monitor = create_monitor(self._team_agent) # 创建 openJiuwen Monitor
await self._monitor.start()
self._running = True
self._event_task = asyncio.create_task(self._collect_events()) # 启动事件收集循环
_collect_events() 是一个无限循环,持续从 self._monitor.events() 读取 SDK 层事件,并转换为前端友好的格式:
# monitor_handler.py _collect_events() 片段
async def _collect_events(self) -> None:
async for event in self._monitor.events(): # 来自 openJiuwen SDK
if not self._running:
break
event_dict = await self._convert_event_to_dict(event)
if event_dict:
await self._event_queue.put(event_dict)
事件转换时,TeamMonitorHandler 会根据事件类型调用不同的 Handler 方法:
@staticmethod
def _handle_member_spawned(base: dict, event: MonitorEvent) -> dict:
base["member_id"] = event.member_id
base["status"] = "running"
base["timestamp"] = event.timestamp or ""
return base
@staticmethod
def _handle_task_claimed(base: dict, event: MonitorEvent) -> dict:
base["task_id"] = event.task_id
return base
4.3.5 创建流处理任务(并发协调)
# team_helpers.py 片段
stream_task = asyncio.create_task(
_consume_stream_with_query(channel_id, session_id, team_agent, query)
)
team_manager.register_stream_task(session_id, stream_task)
_consume_stream_with_query() 内部调用 Runner.run_agent_team_streaming() 启动团队流处理。此时如果 llm_limiter 尚未安装,会触发安装:
# llm_limiter.py install_llm_limiter() 片段
def install_llm_limiter(max_concurrency: int = 2) -> None:
global _semaphore, _original_create, _installed
_semaphore = asyncio.Semaphore(max_concurrency) # 信号量控制并发
from openai.resources.chat.completions import AsyncCompletions
_original_create = AsyncCompletions.create
@wraps(_original_create)
async def _limited_create(self, *args, **kwargs):
async with _semaphore: # 获取信号量后才能调用
for attempt in range(_MAX_RETRIES):
try:
return await _original_create(self, *args, **kwargs)
except Exception as e:
is_429 = "429" in str(e) or "rate" in str(e).lower()
if not is_429 or attempt == _MAX_RETRIES - 1:
raise
delay = _BASE_DELAY * (2 ** attempt) # 指数退避
await asyncio.sleep(delay)
AsyncCompletions.create = _limited_create # Monkey-patch
LLMLimiter 的核心作用 :将所有 LLM API 调用通过 asyncio.Semaphore 限制为最多 2 个并发请求。超出限额的请求会排队等待。当收到 429 错误时,自动按指数退避策略重试(2s → 4s → 8s → 16s → 32s),最多重试 5 次。
4.4 任务执行:团队协作开始
4.4.1 Leader 接收消息,开始任务分解
团队流启动后,Runner.run_agent_team_streaming() 会:
- 调用 Leader 的
interact()方法,传入用户的查询 - Leader 分析任务,发现三个子任务相互独立
- Leader 调用
team.spawn_member工具,依次 spawn 三个 Teammate
spawn 操作会触发事件:MEMBER_SPAWNED
# SDK 事件 → 前端事件映射
SDK_TO_TEAM_EVENT_MAP = {
MonitorEventType.MEMBER_SPAWNED: TeamEventType.MEMBER_SPAWNED,
MonitorEventType.MEMBER_STATUS_CHANGED: TeamEventType.MEMBER_STATUS_CHANGED,
MonitorEventType.MEMBER_EXECUTION_CHANGED: TeamEventType.MEMBER_EXECUTION_CHANGED,
...
}
前端实时收到 team.member.spawned 事件,在成员状态面板中显示新成员加入。
4.4.2 三个 Teammate 并行工作(并发协调 + 能力协调)
三个 Teammate 各自独立调用 LLM API 进行搜索和报告撰写。此时:

并发协调 :如果三个 Teammate 同时调用 DeepSearch,LLMLimiter 的信号量确保最多只有 2 个请求同时发出,第 3 个排队等待。

能力协调:每个 Teammate 上岗时已经继承了主 Agent 的技能:
- 搜索工具(
free_search,paid_search) - 网页获取(
fetch_webpage) - 文件操作(
FileSystemRail)
不需要额外配置,上岗即用。
工具能力卡白名单 (team_runtime_inheritance.py):
TOOL_WHITELIST = frozenset({
"free_search", "fetch_webpage", "paid_search", "vision",
"audio", "image_ocr", "visual_question_answering",
"generate_image", "audio_transcription",
})
只有在这个白名单中的工具能力卡才会被继承给成员。
4.4.3 任务事件全流程记录(事件协调)
任务从创建到完成的每一步都被 Monitor 捕捉并推送:
|-------------------------------|----------|-------------------|
| 事件 | 含义 | 推送时机 |
| team.task.created | 新任务被创建 | Leader 分解任务后 |
| team.task.claimed | 任务被某成员认领 | Teammate 决定处理某任务后 |
| team.member.execution_changed | 成员执行状态变更 | Teammate 开始/结束工作 |
| team.task.completed | 任务完成 | Teammate 提交报告后 |
| team.message.broadcast | 广播消息 | Leader 广播全局指令 |
前端事件面板实时展示这些事件,呈现完整的任务进度。
4.5 追加需求:动态扩容
调研进行中,用户直接追加一条消息:
再帮我加一个方向,Agent 的安全与对齐问题也调研一下。

4.5.1 后续请求的快速路径
后续请求不走 get_or_create_team()(因为团队已存在),而是直接调用 team_manager.interact():
# team_helpers.py process_team_message_stream() 片段
else: # 不是首次请求
if query:
success = await team_manager.interact(session_id, query)
if not success:
yield AgentResponseChunk(...)
TeamManager.interact() 将新消息注入正在运行的团队,Leader 收到消息后:
- 分析出第四个子任务
- Spawn 第 4 个 Teammate(或者复用已有但空闲的成员)
- 新成员同样继承技能和工具,无需 重启 团队
这就是追加交互的价值------动态扩容,无缝衔接。
4.5.2 事件面板追加新任务
事件面板会依次出现:
team.member.spawned(第 4 个成员加入)team.task.created(新任务创建)team.task.claimed(第 4 个成员认领任务)team.task.completed(第 4 个报告完成)
原有的三个成员继续工作,不受干扰。

4.6 查看结果
工作目录最终生成四份报告:
workspace/session/{session_id}/
├── Agent框架对比分析.md
├── Agent通信协议技术对比.md
├── Agent企业落地案例.md
├── Agent安全与对齐.md
└── todo.md




每份报告都是对应 Teammate 独立完成的工作成果。协调机制确保了整个过程中:
- 并发数受控,不会触发 429
- 状态实时可见,任务进度可追踪
- 技能自动继承,无需手动配置
- 动态扩容平滑,无需重建团队
4.7 协调机制全景对照
|--------------------|-------------------|---------------|--------------|-----------------|
| 步骤 | 并发协调 | 分布式协调 | 事件协调 | 能力协调 |
| 启动服务 | - | 识别配置 | - | - |
| 发送首条消息 | - | 分布式模式判断 | - | - |
| get_or_create_team | - | PostgreSQL 检查 | - | - |
| create_team | - | - | - | 注入 Rails + 技能复制 |
| Monitor 启动 | - | - | 事件收集循环 | - |
| 流任务启动 | LLMLimiter 安装 | - | - | - |
| Teammate 工作 | 信号量控流 | - | - | 技能继承 |
| 任务事件 | - | - | 12 种事件推送 | - |
| 追加消息 | - | - | - | 复用现有成员 |
五、写在最后
回到开头的那个场景。三个助手各干各的,互相不知道对方在做什么,一遇挫就全卡住------这几乎是所有多 Agent 系统的通病。
JiuwenSwarm 的做法是把"协调"本身当成一个独立的问题来处理,而不是当作 Agent 的附属功能。LLMLimiter 解决的是资源争抢问题,DistributedRuntime 解决的是跨进程通信问题,MonitorHandler 解决的是状态可视性问题,TeamRuntimeInheritance 解决的是能力复用问题。
当你把这四个问题都解决到位,多 Agent 协作就从"看起来很美"变成了"真的能用"。
Coordination 的价值或许远不止于此。想象一下:如果协调层足够智能,是不是可以让 Agent 自动发现协作模式?能不能根据任务复杂度动态调整成员数量?未来会不会出现专门负责协调的"调度 Agent"?这些问题值得在实践中继续探索。
参考资料: