协同的艺术:JiuwenSwarm Team Agent 的 Coordination 机制解析

想象这样一个场景:你同时让三个助手并行工作,一个查资料、一个写报告、一个做分析。但现实很快会让你头疼------三个人可能同时抢着用同一台打印机,或者各自闷头干活却不知道对方在做什么,又或者你刚布置完任务想追加需求,却发现已经没人理你了。

这恰恰是多 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   # 启动服务

启动后,服务会:

  1. 加载 ~/.jiuwenclaw/config/config.yaml 中的配置
  2. 根据配置初始化 DeepAgent(主 Agent 实例)
  3. 挂载各类 Rails(钩子链)
  4. 启动 WebSocket Gateway,等待前端连接

协调机制在此步骤的体现 :配置加载时,如果 team 配置段存在,系统会调用 config_loader.pyload_team_spec_dict() 预加载团队配置,识别是否需要分布式模式。


第二步:打开前端,切换到集群模式

浏览器打开 http://localhost:5173,看到主界面后:

  1. 在输入框底部找到模式切换按钮
  2. 点击 "👥 集群模式" 按钮

|----|------------|-----------------|
| 按钮 | 模式 | 适用场景 |
| 📋 | 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.pyload_team_spec_dict() 负责从配置文件中读取并合并团队配置,生成一个完整的 TeamAgentSpec 字典,包含:

  • team_name:团队名称
  • lifecycle:生命周期(persistenttemporary
  • teammate_modebuild_moderuntime_mode
  • spawn_modeinprocessdistributed
  • leader: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() 会:

  1. 调用 Leader 的 interact() 方法,传入用户的查询
  2. Leader 分析任务,发现三个子任务相互独立
  3. 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 收到消息后:

  1. 分析出第四个子任务
  2. Spawn 第 4 个 Teammate(或者复用已有但空闲的成员)
  3. 新成员同样继承技能和工具,无需 重启 团队

这就是追加交互的价值------动态扩容,无缝衔接


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"?这些问题值得在实践中继续探索。


参考资料:

相关推荐
七夜zippoe7 小时前
JiuwenSwarm30分钟从零创建Swarm skill并发布到Swarm Skills Hub
ai·skill·openjiuwen·jiuwenswarm·swarm skills
杜子不疼.4 天前
用 JiuwenSwarm 搭建 K8s 集群管理 Agent:Pod 诊断、日志聚合与扩缩容建议
openjiuwen
七夜zippoe8 天前
基于 JiuwenClaw AgentTeam 集群模式的年会策划实战:从源码部署到多智能体协作落地
人工智能·agent·openjiuwen·jiuwenclaw·agentteam
池央2 个月前
JiuwenClaw 完整部署 + 飞书接入教程
飞书·openjiuwen
攻城狮7号2 个月前
用 openJiuwen 安全护栏框架打造财智助手:手把手教你构建金融级 AI 智能体
openjiuwen·安全护栏·财智助手·金融 ai 智能体·合规安全
七夜zippoe2 个月前
智能会议新纪元:JiuwenClaw AI会议管理系统全方位实战
人工智能·技能·skills·openjiuwen·记忆系统·jiuwenclaw
七夜zippoe2 个月前
交叉编码器重排:支持vLLM兼容API的StandardReranker实现
人工智能·vllm·重排·openjiuwen·交叉编码器
●VON4 个月前
0基础也能行!「Flutter 跨平台开发训练营」1月19日正式启动!
学习·flutter·von·openjiuwen
EterNity_TiMe_4 个月前
用 openJiuwen 构建一个历史介绍 AI Agent:从需求到可运行实操
人工智能·开源·实战测评·openjiuwen