---
title: Lightweight Multi-Agent Collaboration Framework Design
date: 2026-03-30
status: proposed
language: zh-CN
---

# 轻量级多 Agent 协作框架设计

## 1. 背景

目标是从零构建一个基于纯 Python 的多 Agent 协作系统。系统不依赖 LangChain、CrewAI、AutoGen 这类重型框架，而是采用 Unix 风格的设计：每个 Agent 只负责单一职责，调度器只负责状态流转、JSON 路由、失败处理和强制中断。

系统面向命令行使用。内部输入输出以 JSON 为主，人类最终阅读的是 Markdown 报告。系统要求 Agent 无状态，所有共享上下文都保存在一个全局白板里。

## 2. 目标

本项目第一版必须支持以下能力：

1. 通过自然语言描述生成 Agent 配置文件。
2. 从 JSON/YAML 配置文件加载多个 Agent。
3. 使用一个 PM Agent 作为会议路由器。
4. 使用共享白板维护会议状态。
5. 从 `meeting-file` 读取主题、背景、决策问题和资料源。
6. 支持从文件和目录收集背景资料，并压缩为会议上下文。
7. 以严格的状态机运行串行会议。
8. 在达到最大轮数或出现失败时进行防御性收尾。
9. 输出 Markdown 会议报告。

## 3. 非目标

第一版不做以下事情：

1. 不做并行 Agent 调度。
2. 不做向量数据库或 RAG 系统。
3. 不做长期记忆存储。
4. 不做自动代码执行或工具调用编排。
5. 不做 Web UI。
6. 不做插件系统。
7. 不做每个 Agent 的完全异构运行时协议。

## 4. 设计原则

### 4.1 极简依赖

只使用：

1. Python 标准库
2. `pydantic`
3. `PyYAML`
4. `openai`
5. `anthropic`

### 4.2 状态分离

Agent 必须是无状态的。Agent 不保存自己的历史，也不直接读取其他 Agent 的原始历史。所有共享信息都由 `MeetingState` 维护。

### 4.3 JSON in, JSON out

运行时内部的所有关键结构都必须可映射为 JSON。包括：

1. Agent 配置
2. PM 决策
3. 普通 Agent 输出
4. 白板状态
5. 事件日志

最终输出给人类的是 Markdown 报告。

### 4.4 强约束结束机制

系统必须通过两层机制防止无限循环：

1. PM 决策中的 `next_action` 只能是 `CALL_AGENT` 或 `FINISH`
2. orchestrator 强制执行 `max_loops`

## 5. 系统架构

项目使用单进程、模块化 CLI 架构。

```text
.
├── agent_builder.py
├── run_meeting.py
├── requirements.txt
├── README.md
├── agents/
├── meetings/
├── reports/
├── core/
│   ├── __init__.py
│   ├── models.py
│   ├── llm.py
│   ├── agent_loader.py
│   ├── context_loader.py
│   └── meeting.py
└── tests/
```

### 5.1 CLI 层

CLI 层只负责参数解析、调用核心模块、打印结果和写文件。

### 5.2 模型层

模型层定义所有 Pydantic 模型，并负责结构校验。

### 5.3 LLM 适配层

LLM 适配层屏蔽 `openai` 和 `anthropic` 的差异，向上层暴露统一接口。

### 5.4 上下文收集层

上下文收集层负责读取 `meeting-file` 中定义的背景信息和资料源，并生成可供会议使用的 `ContextBundle`。

### 5.5 编排层

编排层负责会议状态机、路由、重试、错误记录、强制中断和报告生成。

## 6. 核心数据模型

### 6.1 AgentConfig

描述一个可加载的 Agent。

字段：

- `name: str`
- `role: str`
- `description: str`
- `system_prompt: str`
- `input_schema_description: str`
- `output_schema_description: str`
- `input_schema: dict[str, Any]`
- `output_schema: dict[str, Any]`
- `metadata: dict[str, Any] = {}`

约束：

- `name` 只能使用安全文件名字符。
- `system_prompt` 不允许为空。
- 第一版中，`input_schema` 和 `output_schema` 是强约束，不是纯说明文字。
- 第一版中，普通 Agent 的 `output_schema` 必须与运行时标准结构 `AgentTurnResult` 兼容。
- Loader 在加载 Agent 配置时必须校验这一兼容性；不兼容配置直接报错并拒绝运行。

### 6.2 MeetingBrief

描述共享背景。

字段：

- `project_background: str = ""`
- `current_state: str = ""`
- `constraints: list[str] = []`
- `success_criteria: list[str] = []`
- `non_goals: list[str] = []`

### 6.3 DecisionPacket

描述本次会议需要做出的判断。

字段：

- `decision_to_make: str`
- `options: list[str] = []`
- `evaluation_dimensions: list[str] = []`
- `required_questions: list[str] = []`
- `risk_tolerance: str = ""`
- `time_horizon: str = ""`

约束：

- `decision_to_make` 不能为空。

### 6.4 ContextSource

描述外部资料来源。

字段：

- `type: Literal["file", "directory"]`
- `path: str`
- `purpose: str`
- `include: list[str] = []`
- `exclude: list[str] = []`
- `max_files: int = 20`
- `max_chars_per_file: int = 4000`

约束：

- `path` 不能为空。
- `max_files` 必须大于 0。
- `max_chars_per_file` 必须大于 0。

### 6.5 MeetingInput

描述会议输入文件。

字段：

- `topic: str`
- `brief: MeetingBrief`
- `decision_packet: DecisionPacket`
- `context_sources: list[ContextSource] = []`

约束：

- `topic` 不能为空。

### 6.6 ContextDocument

表示一份被收集到的背景资料。

字段：

- `document_id: str`
- `source_path: str`
- `source_type: Literal["file", "directory_file"]`
- `purpose: str`
- `excerpt: str`
- `char_count: int`

约束：

- `document_id` 必须在一次会议内稳定且唯一。
- 推荐使用规范化相对路径生成 `document_id`。

### 6.7 ContextBundle

表示资料收集后的统一结果。

字段：

- `summary: str`
- `documents: list[ContextDocument]`
- `skipped_paths: list[dict[str, str]] = []`

### 6.8 AgentTurnResult

表示一个普通 Agent 的一次发言结果。

字段：

- `agent_name: str`
- `response: str`
- `key_points: list[str] = []`
- `risks: list[str] = []`
- `recommendations: list[str] = []`
- `citations: list[DocumentCitation] = []`

第一版普通 Agent 运行时统一输出这个结构。Agent 配置中仍保留自己的 schema 描述，但运行时默认要求兼容该结构。

### 6.8.1 DocumentCitation

表示一个可验证的资料引用。

字段：

- `document_id: str`
- `source_path: str`
- `quote: str = ""`

约束：

- `document_id` 必须能映射到 `ContextBundle.documents` 中的某个文档。
- `source_path` 必须与对应文档一致。

### 6.9 PMDecision

表示 PM Agent 的唯一合法输出。

字段：

- `analysis: str`
- `next_action: Literal["CALL_AGENT", "FINISH"]`
- `target_agent: str | None = None`
- `prompt_for_agent: str | None = None`
- `final_report: str | None = None`

约束：

- 当 `next_action == "CALL_AGENT"` 时，`target_agent` 和 `prompt_for_agent` 必填。
- 当 `next_action == "FINISH"` 时，`final_report` 必填。

### 6.10 MeetingState

表示共享白板。

字段：

- `topic: str`
- `participants: list[str]`
- `loop_count: int`
- `max_loops: int`
- `status: Literal["running", "finished", "forced_stop"]`
- `summary: str`
- `key_points: list[str] = []`
- `open_questions: list[str] = []`
- `decisions: list[str] = []`
- `latest_agent_outputs: dict[str, AgentTurnResult] = {}`
- `event_log: list[dict[str, Any]] = []`
- `meeting_input: MeetingInput`
- `context_bundle: ContextBundle`

白板采用双层结构：

1. 摘要层，供 PM 和普通 Agent 使用
2. 事件日志层，供审计和调试使用

额外约束：

- `loop_count` 表示已经完成的普通 Agent 执行轮次数。
- 只有成功或失败地完成一次 `CALL_AGENT` 尝试流程后，`loop_count` 才增加 1。
- 纯 PM 解析重试、PM 路由错误修正不增加 `loop_count`，但受单轮 PM 重试上限约束。

## 7. 配置文件格式

### 7.1 Agent 配置

系统支持 `.json`、`.yaml`、`.yml`。

示例：

```yaml
name: arch
role: Architect
description: 负责从系统边界、复杂度、扩展性和技术风险角度评估方案
system_prompt: |
  你是一个非常严格的软件架构师。
  你关注系统边界、复杂度、演进成本、性能风险和维护成本。
  你的输出必须具体、清晰、可执行。

input_schema_description: |
  输入包含 PM 的具体问题、会议摘要和背景摘要。

output_schema_description: |
  输出必须是结构化 JSON，包含结论、关键点、风险、建议和引用来源。

input_schema:
  type: object
  properties:
    prompt:
      type: string
    meeting_summary:
      type: string
    context_summary:
      type: string
  required:
    - prompt
    - meeting_summary
    - context_summary

output_schema:
  type: object
  properties:
    response:
      type: string
    key_points:
      type: array
    risks:
      type: array
    recommendations:
      type: array
    citations:
      type: array
  required:
    - response

metadata:
  style: architecture_review
```

### 7.2 Meeting 文件

系统支持 `.json`、`.yaml`、`.yml`。

示例：

```yaml
topic: 评估是否要为新的 AI 文档产品构建多 Agent 分析工作流

brief:
  project_background: 我们正在验证一个面向中小团队的 AI 文档协作产品。
  current_state: 目前只有单 Agent 原型，输出深度和稳定性不足。
  constraints:
    - 2 周内要产出 MVP
    - 团队只有 3 名工程师
    - 不希望引入重型框架
  success_criteria:
    - 输出质量明显优于单 Agent
    - 系统足够简单，方便维护
    - 成本可控
  non_goals:
    - 本次不做 UI 产品化
    - 本次不接入向量数据库

decision_packet:
  decision_to_make: 是否用轻量多 Agent 会议系统作为 MVP 核心架构
  options:
    - 继续单 Agent
    - 采用轻量多 Agent
    - 直接引入成熟 Agent 框架
  evaluation_dimensions:
    - 实现复杂度
    - 输出质量
    - 成本
    - 可维护性
    - 扩展性
  required_questions:
    - 多 Agent 是否真能带来足够收益
    - 轻量自研是否比现成框架更合适
    - MVP 应该保留哪些最小能力
  risk_tolerance: 中等
  time_horizon: 两周 MVP

context_sources:
  - type: file
    path: ./README.md
    purpose: 项目概览

  - type: directory
    path: ./docs
    purpose: 已有设计文档
    include:
      - "**/*.md"
    exclude:
      - "**/archive/**"
    max_files: 10
    max_chars_per_file: 3000
```

## 8. Context Sources 设计

会议背景不仅可以来自内联文本，也可以来自文件和目录。

但第一版不允许 Agent 直接无边界读取整个目录。系统会先执行受控收集，再生成 `ContextBundle`。

### 8.1 收集规则

1. 支持 `file` 和 `directory` 两种来源。
2. `directory` 必须通过 `include` 和 `exclude` 做过滤。
3. 默认忽略 `venv/`、`node_modules/`、`.git/` 和其他明显无关的大目录。
4. 每个文件按 `max_chars_per_file` 截断。
5. 每个目录按 `max_files` 限制匹配文件数量。
6. 所有路径都必须相对于 `meeting-file` 所在目录解析，并在内部转换为规范化绝对路径。
7. 对目录匹配结果，必须先按规范化相对路径做字典序排序，再应用 `max_files` 截断。
8. 默认不跟随符号链接；如果命中符号链接，记录到 `skipped_paths`。
9. 无法解码、无法读取、明显是二进制的文件必须跳过，并记录跳过原因。
10. 文本文件默认按 UTF-8 读取；失败后使用 UTF-8 replacement 继续，不再做更多编码猜测。
11. `skipped_paths` 需要记录路径和原因，而不是只记录路径。

### 8.2 设计原因

这种设计可以避免：

1. 上下文过大
2. 结论不可追溯
3. 代码噪音污染会议输入
4. 所有 Agent 盲目读取整个项目树

### 8.3 结果要求

`ContextBundle` 必须记录：

1. 综合摘要
2. 每个文档的来源路径
3. 被跳过的路径

每个 `ContextDocument` 必须包含稳定的 `document_id`，供 Agent 引用和报告追踪使用。

最终报告应能够列出引用过的资料。

## 9. CLI 设计

### 9.1 `agent_builder.py`

用途：把自然语言描述转成 Agent 配置文件。

参数：

- `--description`
- `--name`
- `--role`
- `--provider`
- `--model`
- `--format`
- `--output`

行为：

1. 构造 Agent 生成请求
2. 调用 LLM
3. 解析为 `AgentConfig`
4. 本地校验
5. 输出 JSON 或 YAML 文件

### 9.2 `run_meeting.py`

用途：启动一次多 Agent 会议。

参数：

- `--meeting-file`
- `--topic`
- `--agents`
- `--agents-dir`
- `--provider`
- `--model`
- `--max-loops`
- `--report-file`

规则：

1. `--meeting-file` 和 `--topic` 至少提供一个。
2. 第一版中两者不能同时提供。
3. `--agents` 使用逗号分隔 Agent 名称。

第一版推荐模式是 `--meeting-file`。

如果使用 `--topic` 最小模式，系统必须自动生成一个合法的 `MeetingInput`：

- `brief.project_background = ""`
- `brief.current_state = ""`
- `brief.constraints = []`
- `brief.success_criteria = []`
- `brief.non_goals = []`
- `decision_packet.decision_to_make = topic`
- `decision_packet.options = []`
- `decision_packet.evaluation_dimensions = []`
- `decision_packet.required_questions = []`
- `decision_packet.risk_tolerance = ""`
- `decision_packet.time_horizon = ""`

这样 `--topic` 仍然能映射为合法的 `MeetingInput`，但该模式仅建议用于快速 demo。

## 10. LLM 适配层设计

`core/llm.py` 向上层暴露统一接口。

建议接口：

1. `generate_structured_response(...)`
2. `generate_text_response(...)`

适配层职责：

1. 统一 provider 调用方式
2. 统一异常格式
3. 支持从环境变量读取 API Key
4. 提供有限的结构化重试提示

环境变量：

- `OPENAI_API_KEY`
- `ANTHROPIC_API_KEY`

可选扩展：

- `OPENAI_BASE_URL`

## 11. 会议状态机

状态机使用以下状态：

- `running`
- `finishing`
- `finished`
- `forced_stop`
- `failed`

其中：

- `running` 是正常执行状态
- `finishing` 表示正在进行正常收尾或强制收尾
- `finished` 表示成功产出最终报告
- `forced_stop` 表示本轮会议因保护机制进入强制结束路径
- `failed` 表示连 fallback 报告都无法生成，这在第一版中应尽量避免

### 11.1 初始化

orchestrator 执行以下步骤：

1. 加载 `MeetingInput`
2. 加载 Agent 配置
3. 收集 `ContextBundle`
4. 初始化 `MeetingState`

### 11.2 主循环

每轮执行：

1. 将当前白板摘要发送给 PM Agent
2. 解析并校验 `PMDecision`
3. 如果 `next_action == CALL_AGENT`
   - 找到目标 Agent
   - 组装目标 Agent 输入
   - 调用目标 Agent
   - 校验 `AgentTurnResult`
   - 更新白板和事件日志
4. 如果 `next_action == FINISH`
   - 生成并保存最终 Markdown 报告
   - 将状态设置为 `finished`

每个 PM 决策轮必须遵守以下重试规则：

1. PM 非法 JSON 输出时，最多重试 1 次。
2. PM schema 校验失败时，最多重试 1 次。
3. PM 连续输出未知 `target_agent` 时，单轮最多允许 2 次路由修正。
4. 单轮 PM 重试耗尽后，直接进入 `finishing`，由 orchestrator 触发 fallback 报告路径。

### 11.2.1 有限状态转移

```text
running --PM FINISH--> finishing --report saved--> finished
running --PM CALL_AGENT--> running
running --loop_count >= max_loops--> finishing
running --PM invalid exhausted--> finishing
finishing --PM forced finish success--> forced_stop
finishing --PM forced finish invalid--> forced_stop
forced_stop --fallback report saved--> finished
```

说明：

- 正常 `FINISH` 走 `finished`
- 达到 `max_loops` 或 PM 行为失控时走 `forced_stop`
- fallback 报告成功落盘后，最终状态统一记为 `finished`，并在报告中保留 `Process Note`

### 11.3 PM 输入边界

PM 只能看到：

1. `topic`
2. `decision_packet`
3. 白板摘要
4. 可调用参与者列表
5. `mode: "normal" | "forced_finish"`
6. `remaining_loops: int`
7. `last_error: str | None`

PM 不看全量原始历史，以防 token 失控和行为漂移。

### 11.4 普通 Agent 输入边界

普通 Agent 只能看到：

1. PM 的具体问题
2. 当前白板摘要
3. 背景摘要

普通 Agent 不直接读取全量事件日志。

## 12. 白板更新策略

每次普通 Agent 成功返回后，orchestrator 需要：

1. 在 `event_log` 追加原始事件
2. 更新 `latest_agent_outputs`
3. 按确定性规则更新：
   - `summary`
   - `key_points`
   - `open_questions`
   - `decisions`

第一版不单独引入摘要 Agent。摘要更新逻辑使用确定性 Python 代码完成，以降低复杂度和成本。

确定性合并规则如下：

1. `latest_agent_outputs[target_agent]` 只保留该 Agent 最新一次成功结果。
2. `key_points`、`open_questions`、`decisions` 按首次出现顺序追加，并使用完整字符串做去重。
3. `open_questions` 中以问号结尾，或以“是否”“如何”“为什么”等不确定表达开头的条目优先保留。
4. 如果某个 `recommendation` 明确回答了已有 `open_questions` 中的同一主题，只有在实现中能通过完全相同字符串匹配时才移除对应问题；第一版不做语义级消解。
5. `summary` 由以下固定模板重写，而不是无限追加：
   - 第一行：当前 topic
   - 第二行：最近一次成功发言的 Agent 和一句结论摘要
   - 第三行：累计关键点数量、未决问题数量、候选决策数量
6. `summary` 最大长度为 1200 字符；超出时从末尾截断。
7. 每个列表字段最大保留 20 条；超出后丢弃更晚的新增项。

这些规则必须足够稳定，以便写出确定性的单元测试。

## 13. 错误处理与重试

### 13.1 普通 Agent 失败策略

默认策略：自动重试 1 次，仍失败则记录错误并回到 PM。

失败类型包括：

1. LLM 调用失败
2. JSON 解析失败
3. Schema 校验失败

处理流程：

1. 第一次失败后进行一次重试
2. 第二次失败后写入 `agent_error` 事件
3. 将错误摘要保留在白板中
4. 让 PM 再次决策是否继续或结束

### 13.2 PM 路由错误

如果 PM 输出的 `target_agent` 不存在：

1. 记录 `pm_routing_error` 事件
2. 不执行普通 Agent 调用
3. 返回 PM 再次决策

如果单轮中连续两次发生 `pm_routing_error`，则视为该轮 PM 决策失败，直接进入 `finishing`。

## 14. 强制中断与收尾

### 14.1 最大轮数保护

当 `loop_count >= max_loops` 时，orchestrator 必须禁止继续 `CALL_AGENT`。

### 14.2 优先收尾策略

优先让 PM 在 `mode="forced_finish"` 的约束下输出最终报告。

### 14.3 Fallback 收尾策略

如果 PM 在强制收尾时仍输出非法结果，orchestrator 使用本地模板生成一份 Markdown 报告。

这可以保证系统无论如何都有明确产出，而不是死在最后一步。

fallback 报告最少必须包含：

1. Topic
2. Current Summary
3. Key Findings
4. Open Questions
5. Candidate Decisions
6. Evidence Reviewed
7. Process Note

## 15. 报告格式

最终 Markdown 报告建议结构：

```md
# Meeting Report

## Topic
...

## Executive Summary
...

## Key Findings
- ...

## Risks
- ...

## Open Questions
- ...

## Recommendation
...

## Evidence Reviewed
- ...
```

如果报告来自强制中断路径，应额外包含：

```md
## Process Note
Meeting stopped after reaching max loop limit before full convergence.
```

## 16. Demo 路径

README 应提供一个最小 Demo：

1. 安装依赖
2. 设置 API Key
3. 创建 `arch`、`biz`、`pm` 三个 Agent
4. 编写 `meetings/demo.yaml`
5. 运行 `run_meeting.py`
6. 查看 `reports/demo.md`

## 17. 测试策略

系统必须优先通过测试驱动实现。

建议覆盖：

1. `PMDecision` 条件校验
2. `AgentConfig` 和 `MeetingInput` 校验
3. Agent 配置加载
4. Context source 过滤与截断
5. 单轮 `CALL_AGENT` 流程
6. Agent 失败后自动重试
7. `max_loops` 强制收尾
8. Markdown 报告落盘
9. PM 非法 JSON 输出
10. PM schema 非法输出
11. PM 连续路由到未知 Agent
12. PM-only 错误轮不会无限消耗循环
13. `mode="forced_finish"` 的 PM 路径
14. fallback 报告路径
15. 白板合并规则的确定性行为
16. Context source 的排序、符号链接、二进制文件、不可读文件和编码失败场景

测试中的 LLM 应使用假实现，不依赖真实网络。

## 18. 实现顺序

建议实现顺序：

1. 定义全部 Pydantic 模型
2. 编写模型测试
3. 实现 Agent 配置加载器
4. 实现会议输入和 context loader
5. 实现 LLM 适配层
6. 实现 `agent_builder.py`
7. 实现 `run_meeting.py` 的主循环
8. 实现报告落盘和 demo

## 19. 已确认的关键决策

本设计基于以下已经确认的约束：

1. 同时支持 `openai` 和 `anthropic`
2. Agent 配置同时支持 JSON 和 YAML
3. 第一版运行时严格串行，但保留未来并行扩展空间
4. Agent schema 同时保留自然语言描述和 JSON Schema
5. 白板使用摘要层加事件日志层的双层结构
6. 普通 Agent 失败时自动重试 1 次，仍失败则显式记录
7. 会议背景支持目录和文件作为受控资料源

## 20. 风险

第一版最可能出现的问题：

1. LLM 对严格 JSON 输出的稳定性不足
2. 长文档摘要质量波动
3. 白板摘要规则过于简单，导致 PM 收敛质量不足
4. 不同 provider 的结构化输出能力存在差异

对应缓解措施：

1. 对 PM 和普通 Agent 使用明确 schema 和重试提示
2. 限制资料体积
3. 先使用确定性白板更新规则，避免额外智能层
4. 在适配层统一错误处理并保留 fallback

## 21. 结论

这个方案在保持极简依赖和 Unix 风格的同时，提供了一个足够完整的多 Agent 协作框架最小闭环。第一版的重点不是“功能多”，而是让以下路径稳定可用：

1. 自然语言生成 Agent 配置
2. 会议输入加载和资料收集
3. PM 串行调度普通 Agent
4. 共享白板驱动会议推进
5. 受控收尾和 Markdown 报告输出

只要这个闭环稳定，后续再扩展并行、更多角色和更复杂的输入协议，就会有清晰的演进路径。
