Agent 配置化系统
Agent 是数据,不是类 — 一个 Markdown 文件就是一个 Agent。
Agent-as-Config 理念
ArtifactFlow 的 Agent 完全由配置驱动:每个 Agent 是 config/agents/ 下的一个 .md 文件,包含 YAML frontmatter(元数据)和 Markdown body(角色提示词)。不需要编写任何 Python 代码。
config/agents/
├── lead_agent.md # 协调者
├── research_agent.md # 大型知识探索/整合(上下文隔离)
└── compact_agent.md # 对话摘要(内部)
AgentConfig 数据结构
src/agents/loader.py 定义了 AgentConfig dataclass:
@dataclass
class AgentConfig:
name: str # Agent 唯一标识
description: str # Agent 描述(用于 call_subagent 候选列表)
tools: dict[str, str] = field(default_factory=dict) # {tool_name: permission_level}
model: str = "qwen3.6-plus-no-thinking" # LLM 模型别名
max_tool_rounds: int = 3 # 最大工具调用轮数
internal: bool = False # 内部 Agent(不出现在候选列表)
role_prompt: str = "" # MD body(角色提示词)
YAML Frontmatter Schema
每个 Agent 的 .md 文件以 YAML frontmatter 开头:
---
name: research_agent
description: |
Large-scale knowledge exploration and multi-source integration
- Context-isolated deep research
- Search → fetch → read loops
tools:
web_search: auto
web_fetch: confirm
read_artifact: auto
grep_artifact: auto
model: qwen3.6-plus
max_tool_rounds: 50
---
字段参考
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
name |
string | 是 | — | Agent 唯一标识(与文件名无关,以此字段为准) |
description |
string | 否 | "" |
Agent 描述,注入到 <available_subagents> 列表中 |
tools |
dict | 否 | {} |
工具白名单 + 权限覆盖,格式 {tool_name: auto\|confirm} |
model |
string | 否 | "qwen3.6-plus-no-thinking" |
引用 config/models/models.yaml 中的模型别名 |
max_tool_rounds |
int | 否 | 3 |
最大工具调用轮数,超过后注入系统提示要求总结 |
internal |
bool | 否 | false |
内部 Agent,不出现在 call_subagent 的可用候选列表中 |
工具权限覆盖
tools 字段不仅是工具白名单,也是 per-agent 的权限覆盖:
引擎执行工具时的权限决策逻辑:
- 检查 agent 配置中该工具的权限值(
agents[agent_name].tools[tool_name]) - 如未配置,回退到工具自身的默认权限(
tool.permission) - 如果最终权限为
CONFIRM且工具不在always_allowed_tools中 → 触发权限中断
Role Prompt 设计
YAML frontmatter 之后的 Markdown body 是 Agent 的角色提示词,会作为 system prompt 的第一层注入到每次 LLM 调用中。
以 lead_agent.md 为例:
---
name: lead_agent
...
---
<role>
You are lead_agent, the Lead Agent coordinating a multi-agent system.
**Execution Flow:**
1. **Analyze Request** — Determine complexity
2. **Plan Tasks** — Create task_plan if needed
3. **Execute** — Call sub-agents or work directly
4. **Integrate** — Update result artifact with findings
5. **Iterate** — Refine based on progress and feedback
</role>
<task_plan>
For tasks requiring multiple steps or sub-agent calls, create a task_plan artifact...
</task_plan>
<artifacts>
You can create MULTIPLE result artifacts...
</artifacts>
编写建议:
- 用 XML 标签分隔不同关注点(
<role>,<task_plan>,<output_format>等) - 明确职责边界("你是什么"、"你不做什么")
- 定义输出格式(减少 LLM 输出的不确定性)
- 设置停止条件(如
research_agent的<output>收敛要求 +max_tool_rounds兜底)
现有 Agent 概览
| Agent | 职责 | 工具 | 模型 | 最大轮数 | 内部 |
|---|---|---|---|---|---|
lead_agent |
任务协调、规划、Artifact 管理、subagent 路由 | create/update/rewrite/read/grep_artifact + web_search + web_fetch + call_subagent | qwen3.6-plus | 100 | 否 |
research_agent |
大型知识探索 / 多源整合,在隔离上下文中执行 | create/update/rewrite/read/grep_artifact + web_search + web_fetch | qwen3.6-plus | 50 | 否 |
compact_agent |
对话摘要生成(Compaction) | 无 | qwen3.6-plus | 0 | 是 |
角色分工
- lead_agent 是唯一与用户直接交互的 Agent,也是唯一能创建/修改最终 Artifact 的入口;自身可直接执行
web_search/web_fetch处理小规模查询。 - research_agent 是执行型 subagent,由 lead_agent 通过
call_subagent分发任务。和 lead 工具集几乎相同(除call_subagent),其存在意义是上下文隔离:把需要 ≥3 来源 / 多步 search→fetch→read 循环的大型探索任务从 lead 的上下文中剥离,避免大量中间 fetch 结果污染主对话。 - compact_agent 是内部 Agent,由
CompactionRunner在引擎循环内每次 LLM 调用后同步触发(超阈值时),输出结构化摘要作为COMPACTION_SUMMARY事件追加到state["events"]尾部,详见 engine.md → Compaction 机制
Agent 协作模型
sequenceDiagram
participant User
participant Lead as lead_agent
participant Sub as research_agent
User->>Lead: 用户请求
Lead->>Lead: 分析任务, 创建 task_plan
Lead->>Sub: call_subagent(instruction)
Note over Lead,Sub: current_agent 切换为 subagent
Sub->>Sub: 执行工具 (web_search / web_fetch / read_artifact / grep_artifact)
Sub-->>Lead: 返回结果 (subagent_result XML)
Note over Lead,Sub: current_agent 切回 lead_agent
Lead->>Lead: 整合结果, 更新 Artifact
Lead-->>User: 最终响应
协作流程:
- Lead 分发:Lead 调用
call_subagent工具,提供agent_name和instruction - Agent 切换:引擎将
state["current_agent"]设为目标 subagent,instruction 作为SUBAGENT_INSTRUCTION事件注入 - Sub 执行:Subagent 不看 lead 的对话历史,只看自己那部分 agent_name-filtered 事件(instruction、LLM 响应、tool results)。
EventHistory扫 boundary 时对 subagent 还会停在SUBAGENT_INSTRUCTION.fresh_start=True上 —call_subagent的fresh_start默认 True,所以 Lead 默认每次调用都给 subagent 一个干净起点;仅当 Lead 显式传fresh_start=false时,第二次调用才能延续第一次调用的上下文,直到最近的COMPACTION_SUMMARYboundary(如第一次调用中途触发过 compaction,第二次看到的仍是摘要 + 其后的事件,而非第一次的完整原始上下文) - 结果回传:Subagent 无工具调用时,响应打包为
<subagent_result>XML,作为call_subagent的 tool_result 返回给 Lead - Lead 继续:Lead 看到 tool_result 后决定下一步(继续分发、整合结果、或完成)
Agent 注册与加载
加载机制
load_all_agents()(src/agents/loader.py)在服务启动时扫描 config/agents/ 目录:
def load_all_agents(agents_dir=None) -> dict[str, AgentConfig]:
# 默认从 {project_root}/config/agents/ 加载
# 遍历所有 .md 文件,按 sorted() 顺序
# 解析 YAML frontmatter + MD body
# 返回 {agent_name: AgentConfig} 字典
- 文件名不重要,以 frontmatter 中的
name字段为准 - 单个文件解析失败不影响其他 agent 的加载(错误会 log)
- 加载结果传递给
execute_loop()的agents参数
单文件解析
load_agent(md_path) 的解析逻辑:
- 检查文件以
---开头 - 找到第二个
---,之间的内容用yaml.safe_load解析为 frontmatter - 第二个
---之后的内容作为role_prompt(strip 后赋值)
Design Decisions
为什么 Agent 是数据不是类
- 降低扩展门槛:添加新 Agent 只需创建一个
.md文件,不需要理解 Python 代码结构 - 热加载潜力:配置文件的修改不需要改动代码,未来可以实现运行时重载
- 关注点分离:Agent 的行为完全由提示词决定,执行逻辑统一在引擎中处理
- 可审查性:所有 Agent 的配置集中在
config/agents/,一目了然
完成路由不对称性的意图
- Lead 是唯一出口:只有 Lead Agent 的无工具调用才会终止执行循环
- Subagent 必须回传:Subagent 完成后其响应作为工具结果返回给 Lead,由 Lead 决定下一步
- 统一控制流:所有的任务调度、结果整合、最终响应都经过 Lead,避免 subagent 直接对用户输出
- 这个设计使得 Lead 拥有全局视角,可以在多个 subagent 结果之间做出取舍和整合