文件系统即协议:FCoP 如何用最简 Markdown 实现 Agent 治理?

22 阅读20分钟

文件系统即协议:FCoP 如何用最简 Markdown 实现 Agent 治理?

文件系统即协议:FCoP 如何用最简 Markdown 实现 Agent 治理?

在上一篇中,我们探讨了 2026 年多 Agent 协同落地时面临的“通信迷局”与治理焦虑:当越来越多智能体在后台通过复杂的 JSON、YAML 与黑盒 API 持续交换状态、任务和工具调用时,人类管理者正在变得越来越难以直接看清——工作究竟交给了谁、执行到了哪里、依据什么继续,以及出了问题之后该由谁负责。

既然大厂和前沿研究都在转向“人类可读契约”与“过程透明化”,那么在工程落地中,究竟该如何实现一套轻量、优雅且具备工业级约束力的治理方案?

今天,我们把目光聚焦于我们自主设计和孵化的开源协议——FCoP(File-based Coordination Protocol),看看它如何打破对于复杂基础设施的迷信,用最普通的 Markdown、目录和文件系统,建立一套能够真正约束多 Agent 协作的协议机制。


一、为什么重新回到 Unix 文件哲学?

FCoP 选择文件系统,一个重要的思想来源是 Unix 哲学。Unix 并不追求把所有能力都塞进一个巨大的中心程序,而是倾向于让不同工具分别做好一件事情,通过简单、稳定的接口交换信息,再由这些小工具组合成更复杂的系统。

FCoP 把这种思路带进了多 Agent 协作。在协议中,TASK 负责表达任务,REPORT 负责表达交付,ISSUE 记录尚未解决的问题,REVIEW 则记录审查与决定;Markdown 既可以直接被人阅读,也可以通过结构化字段被程序解析。

因此,“文件系统即协议”并不是简单照搬 Unix 世界里的“万物皆文件”。FCoP 真正继承的是 Unix 背后更重要的几个设计原则:文本接口、小而明确的职责,以及可以组合的工具。

当 Agent 系统越来越复杂时,人们很容易形成一种直觉:既然运行系统越来越复杂,那么治理系统也应该越来越复杂,于是继续增加数据库、状态服务、消息队列、事件总线和工作流引擎。FCoP 从另一个方向提出问题:一项工作要在多个 Agent 之间可靠流转,真正不可缺少的事实究竟有多少?

下一位责任角色真正需要确认的,其实无非是:这是什么任务,目前由谁负责,现在处于哪一次执行,已经产生了什么交付,谁检查过这份交付,以及这份审查是否仍然适用于当前工作。如果这些事实可以稳定地保存、重新读取并被程序验证,那么治理并不一定必须从一个庞大的中心系统开始。

所以,FCoP 真正尝试做的,并不是把复杂工作流重新发明一遍,而是把多 Agent 协作中最关键的责任关系压缩成少量稳定事实,再让协议控制这些事实什么时候可以发生变化。

微软在 Build 2026 公布的 ASSERT 与 Agent Control Specification(ACS),也把“发现问题”和“在运行路径上实施控制”区分开来:先通过评估发现缺陷,再在输入、状态、工具执行和输出等关键位置施加控制。ASSERT 与 ACS 技术说明

FCoP 并不是 ACS 的实现,两者的架构和适用范围也不同,但它们体现了一个相似的工程原则:治理不能只发生在事后,也不能只留下日志,而必须能够在工作真正向前推进的关口产生约束。

当然,FCoP 也不主张文件系统替代所有基础设施。跨机器高吞吐通信、复杂查询、海量事件流、分布式事务和弱一致网络环境,依然更适合数据库、消息队列或专门的协调系统。FCoP 处理的是另一层更基础的问题:在一个项目工作空间中,怎样让任务、交付、审查和责任关系既能被人直接读懂,也能被机器重新验证。


二、FCoP 保存的不是聊天,而是工作事实

FCoP 并不试图保存 Agent 的全部对话,更不需要保存 Agent 的全部思考过程。它关心的是,当工作从一个角色交给下一个角色时,后者真正需要依赖哪些已经确认的事实。

最核心的记录可以概括为四类:

文件保存什么
TASK工作内容、委托关系与生命周期
REPORT某一次执行产生的交付
ISSUE执行过程中尚未解决的问题
REVIEW对具体交付作出的审查与决定

如果把一个典型的多 Agent 软件团队简化,可以得到下面这样的关系:

ADMIN ⇄ PM                沟通需求与管理决策
          │
          ▼
      FCoP 协议层
      ├─ PM:创建和管理 TASK,读取交付与审查
      ├─ DEV:领取 TASK,形成 REPORT / ISSUE
      └─ QA :读取 TASK 与 REPORT,形成 REVIEW

注:这里的角色只用于说明一种典型协作关系,并不是 FCoP Core 固定定义的角色集合。

这里真正发生变化的,并不是把聊天换成了 Markdown,而是把“角色自己的运行上下文”与“团队共同依赖的正式工作事实”分离开来。

PM 不需要进入 DEV 的会话翻阅几十轮聊天记录,DEV 也不需要在完成工作之后,再向 QA 重新口头解释整个过程。不同角色真正交接的不是上一段对话,而是已经进入协议层的 TASK、REPORT、ISSUE 和 REVIEW。

这样一来,Agent 的运行上下文可以结束,模型可以更换,会话甚至可以完全消失,但工作本身仍然存在。下一位责任角色重新读取协议文件,依然能够知道任务从哪里来、当前执行到了什么阶段、交付是什么,以及谁曾经对它做出过审查。

这也是 FCoP 与许多“多 Agent 聊天框架”最根本的区别之一:协作事实不依赖会话继续存在。

FCoP 4.0 协议架构图

图 1 FCoP 4.0 协议架构图。FCoP 通过 Agent Host 接入,由协议工具提供能力入口,协议核心负责检查与约束,最终将被确认的协议事实持久化为文件及生命周期状态。


三、五桶不是目录约定,而是协议状态

理解 FCoP,最容易产生的一个误解,是把 inbox、active、review、done 和 archive 看成五个用于整理文件的普通目录。

实际上,它们承担的是 TASK 的生命周期状态。

_lifecycle/
├─ inbox/
├─ active/
├─ review/
├─ done/
└─ archive/

inbox 表示任务已经进入系统但尚未被认领,active 表示任务已经进入执行,review 表示当前执行已经形成交付并等待审查,done 表示任务已经满足完成条件,archive 则保存已经结束并退出当前工作视野的历史任务。

一项任务的典型生命周期可以表示为:

inbox → active → review → done → archive
           ↑         │
           └─────────┘
               返工

五桶设计真正重要的地方,不在于用了五个文件夹,而在于状态由位置物化。TASK 当前处于哪个生命周期位置,就对应哪个协议阶段,因此系统不需要再依赖某个 Agent 自己解释“我现在做到哪一步了”。

但这并不意味着 Agent 可以直接移动这些文件。恰恰相反,如果 Agent 可以自己把 TASK 从 active 移到 done,那么五桶就只是一套目录约定,而不是治理协议。

FCoP 真正增加的约束是:Agent 可以提出动作,但不能直接改变生命周期事实。

执行者可以请求认领任务、提交交付,审查者可以提交 REVIEW 或批准结果,但这些动作是否足以让 TASK 进入下一个阶段,要由 FCoP 根据当前状态、当前执行、交付、审查和授权关系重新验证。只有条件满足,协议层才执行对应的生命周期迁移,并把新的状态物化到文件系统中。

整个过程可以概括为:

Agent 提出协议动作
        ↓
FCoP 检查当前事实与协议条件
        ↓
      允许 / 拒绝
        ↓
允许时执行生命周期迁移
        ↓
新的文件位置成为新的可观察状态

因此,“文件系统即协议”并不是说文件系统自己会作出裁决,而是说文件系统承载和物化协议事实,FCoP 负责决定哪些事实变化可以成立。

这正是 FCoP 作为“协议”而不仅仅是一套 Markdown 格式的关键所在。


四、五桶解决“在哪里”,attempt 解决“第几次”

五桶可以告诉系统一项 TASK 当前处于哪个生命周期阶段,但它还不能回答另一个非常重要的问题:如果同一项任务被退回重新执行,现在看到的交付究竟属于哪一轮?

这就是 attempt_id 的作用。

当 TASK 开始一次新的执行时,系统会为这一轮执行建立对应的 attempt_id。如果任务经过审查以后被退回并再次进入执行,就会形成新的执行轮次。

因此,task_id 和 attempt_id 解决的是两个不同的问题:task_id 确认“是不是同一项工作”,attempt_id 则确认“是不是同一次执行”。

例如:

TASK-A
│
├─ attempt-1
│    ├─ REPORT-1
│    └─ REVIEW-1
│
└─ attempt-2
     └─ REPORT-2

这里 TASK-A 始终是同一项任务,但 attempt-1 和 attempt-2 已经是两次不同执行。

这一区分非常重要,因为现实中的任务很少一次通过。第一轮实现可能存在问题,第二轮修改以后会产生新的 REPORT,甚至还可能继续经历第三轮返工。任务本身没有改变,但每一轮执行对应的交付事实已经不同。

如果系统只认识 task_id,就很容易把第一轮的 REPORT、测试结果甚至 REVIEW 错误地继续应用于第二轮。FCoP 用 attempt_id 把这些执行轮次分开,因此第一轮产生的 REPORT 和 REVIEW 可以继续作为历史证据存在,却不会自动替第二轮交付作证。

可以把两者的分工理解得很简单:五桶解决任务现在在哪里,attempt 解决这是这项任务的第几次执行。


五、把身份、关系和状态拆开

文件名也是理解 FCoP 演进的一个直观入口。

早期设计中,文件名承担过较多的人类可读信息,例如把发送方和接收方直接写进名称:

TASK-...-ADMIN-to-PM.md
TASK-...-PM-to-DEV.md
REPORT-...-DEV-to-PM-....md

这种方式很直观,人打开目录以后甚至不需要读取文件正文,就能大致判断这是谁发给谁的工作。但它也意味着记录身份、角色路由和业务语义逐渐挤进同一个文件名,一旦团队角色发生变化,命名规则也需要跟着变化,协议更容易与某一种固定的组织结构绑定。

FCoP 4.x 的设计方向,是把这些职责拆开。为了理解这种结构,可以把记录文件看成:

TASK-<stable-id>.md
REPORT-<stable-id>.md
REVIEW-<stable-id>.md

这里的 <stable-id> 用来说明稳定身份这一设计思想,并不试图枚举某一个具体版本的全部正式文件名细节。文件名主要负责回答“这是什么类型的记录,以及这条记录本身是谁”,而发送方、接收方、所属任务和执行轮次,则由结构化字段表达。

例如一份 REPORT 可以包含:

sender: DEV
recipient: PM
subject_ref: TASK-...
attempt_id: urn:uuid:...

于是,PM 不需要从文件名中的 DEV-to-PM 判断某份 REPORT 是否与自己有关,而可以依据协议关系识别它由谁产生、交给谁、属于哪项任务,以及来自哪一次执行。

这种设计可以概括为一句话:文件名负责身份,结构化字段负责关系与路由,生命周期路径负责状态。

相同的思想也延伸到了操作层。Agent 系统中的工具调用可能因为超时被重复提交,如果系统不能判断“这是不是刚才那一次操作”,一次普通重试就可能制造第二项工作。

FCoP 因此使用 operation_id 标识一次操作。相同 operation_id 和相同内容再次提交时返回已有结果,而复用相同 operation_id 却修改请求内容,则会被拒绝。

本轮实验中,同一创建请求连续提交 25 次,最终只产生 1 个 TASK;保持 operation_id 不变但修改正文再次提交,系统返回:

OPERATION_ID_CONFLICT

这里遵循的仍然是同一个原则:先确定稳定身份,再围绕这个身份建立关系和状态约束。一次网络重试,不应该因此变成另一项工作。


六、YAML 真正表达的不是配置,而是责任关系

FCoP 使用 Markdown 保存正文,同时通过 YAML frontmatter 描述协议关系。一份 REPORT 的核心结构可以类似这样:

---
protocol: fcop
version: 4
type: REPORT
report_id: REPORT-...
workspace_id: urn:uuid:...
sender: DEV
recipient: PM
subject_ref: TASK-...
attempt_id: urn:uuid:...
report_kind: final
result: done
references: []
---

第二轮返工后的交付。

如果只看形式,它很像一份普通的结构化 Markdown,但真正重要的不是字段数量,而是这些字段建立了怎样的责任关系。

report_id 说明这份交付本身是谁;subject_ref 说明它属于哪一项 TASK;attempt_id 说明它来自这项任务的哪一次执行;sender 和 recipient 则说明这条记录由谁产生,又交给谁处理。

REVIEW 使用的是同样的逻辑。一份 REVIEW 不能只有一句 approved,因为系统真正需要确认的不是有没有出现“批准”这个词,而是谁批准了什么、批准的是哪项任务、哪一次执行、哪一份 REPORT,以及这份批准现在是否仍然有效。

因此,REVIEW 必须与相应的 TASK、attempt 和被检查的 REPORT 建立明确关系,授权本身也必须具有自己的适用范围。这样,“approved”才不是一句孤立的自然语言,而是一条可以重新检查的工作事实。

这也是为什么,在 FCoP 中,“任务以前通过过审查”和“当前这一轮交付已经通过审查”不是同一件事。


七、协议真正发挥作用的地方,是阻止错误状态成立

FCoP 的核心并不是让正确流程看起来顺畅,而是在条件不成立时明确拒绝错误动作。

Agent 可以说“我已经完成”,也可以请求把任务提交审查,但协议不会因为一句自然语言声明就接受这个结果。FCoP 会重新检查当前 TASK、attempt、REPORT、REVIEW 和授权关系,只有当前事实能够支持这次迁移,生命周期才会继续向前。

例如,执行者认领任务时可以发起类似这样的调用:

project.transition(
    task_id=task_id,
    from_stage="inbox",
    to_stage="active",
    tool="claim_task",
    actor=actor,
)

真正重要的不是这几行代码,而是这次请求背后的前提是否仍然成立。如果某个进程已经成功认领了 TASK,使它离开 inbox 进入 active,另一个并发执行者再尝试执行同样的认领动作时,FCoP 会重新读取当前事实,发现原来的 inbox → active 前提已经不存在,于是拒绝第二次迁移。

同样,如果当前 attempt 尚未产生 REPORT,那么任务就不能凭空进入审查;如果 REVIEW 检查的是上一轮 REPORT,也不能拿它批准这一轮新的交付。

这正是协议存在的意义:Agent 可以提出判断和请求,但不能自行把自己的判断变成系统事实。


八、实验:这些 Markdown 和目录约束究竟能挡住什么?

为了验证这些约束是否真的能够执行,而不仅仅停留在协议说明上,我们在 FCoP 4.0.3 上进行了多组本地实验。测试环境为 Windows 本地文件系统、Python 3.10.11 与 fcop==4.0.3。

主要结果如下:

检查结果
10 轮、每轮 4 个进程同时认领10 次成功,30 次 INVALID_TRANSITION
没有当前 REPORT 就提交审查REPORT_REQUIRED
使用另一轮执行产生的 REPORTATTEMPT_MISMATCH
返工后继续使用旧 REVIEW 批准新交付AUTHORIZATION_INVALID

第一组测试验证的是并发认领。每轮同时启动 4 个进程争抢同一个 TASK,共执行 10 轮,也就是总计 40 次竞争。结果是每轮只有一个进程认领成功,其余三个均收到 INVALID_TRANSITION,最终统计为 10 次成功、30 次拒绝,没有出现同一 TASK 被多个执行者同时成功认领的情况。

第二组和第三组测试针对的是证据边界。如果当前执行尚未产生 REPORT,系统不会允许直接进入审查,而是返回 REPORT_REQUIRED;如果拿另一轮执行产生的 REPORT 来替代当前交付,则会返回 ATTEMPT_MISMATCH。

第四组测试更能体现返工后的治理意义。

假设第一轮执行形成:

TASK-A
  └─ attempt-1
       └─ REPORT-1
            └─ REVIEW-1

QA 对 REPORT-1 完成审查以后发现问题,于是任务被退回重新执行,形成新的执行轮次:

TASK-A
  └─ attempt-2
       └─ REPORT-2

此时,TASK-A 仍然是同一项任务,但执行已经从 attempt-1 变成 attempt-2,交付也已经从 REPORT-1 变成 REPORT-2。旧的 REVIEW-1 虽然是真实发生过的历史记录,却只能证明第一轮的 REPORT-1 曾经被检查,不能证明第二轮的 REPORT-2 也已经通过审查。

如果试图继续使用旧 REVIEW 推进新的交付,FCoP 会返回:

AUTHORIZATION_INVALID

这条规则保护的其实是一个非常朴素的责任边界:旧审查是真实发生过的事实,但它不是当前交付的授权。

因此,文件当然可以被读取,正文里也可以写“完成”或者“通过”,但生命周期不会因此自动向前推进。真正决定工作能否继续的,是当前状态、当前执行、当前交付与当前授权是否一致。


九、“文件系统即协议”究竟意味着什么?

到这里,“文件系统即协议”这句话可以被更准确地理解。

它不是把所有聊天都保存成 Markdown,也不是让 Agent 自己在几个目录之间搬动文件,更不是认为文件系统本身能够作出业务裁决。

FCoP 真正形成协议,是因为几个层面的约束同时存在:文件拥有稳定身份,结构化字段保存明确关系,五桶物化生命周期状态,attempt 区分不同执行轮次,而所有状态变化都必须通过协议动作和条件检查。

换句话说,Markdown 保存工作内容,结构化字段保存责任关系,文件路径物化生命周期状态,而 FCoP 协议决定这些状态什么时候允许发生变化。

因此,FCoP 的“极简”并不意味着没有规则,也不是减少治理要求。它只是把复杂性放在真正需要存在的位置:身份、关系、执行轮次和迁移边界,而不是把所有复杂性堆进一个中央工作流系统。

这也是为什么 FCoP 可以使用最普通的文件和 Markdown,却仍然形成真正的治理约束。


十、FCoP 解决什么,又不解决什么?

FCoP 并不能替代测试,也不能因为 REPORT 中写了一句“所有测试已经通过”,就自动知道测试是否真的运行过。它同样不会替代 QA、人工审查或独立评价,也不会自动判断一项技术结论是否真实。

这些内容层面的真实性,仍然需要测试系统、代码审查、QA、独立评价或其他证据机制完成。

FCoP 解决的是这些判断发生之前更加基础的一层问题:我们现在面对的是哪一项任务,这份交付来自哪一次执行,当前 REVIEW 检查的究竟是哪一份 REPORT,以及这份授权现在是否仍然适用于当前工作。

这些问题听起来并不“智能”,甚至非常传统。但如果连这些最基础的关系都无法确定,再强大的模型、Agent 或工作流系统,也可能在错误的对象上继续运行。


十一、结语:协议的价值,是不让 Agent 自己定义事实

Agent 系统正在迅速变复杂。模型越来越多,工具越来越多,角色越来越多,自动执行越来越普遍,跨系统连接也越来越深入。面对这样的趋势,人们很容易产生一个直觉:既然 Agent 系统本身正在变得复杂,那么治理它的系统也应该建立成同样复杂的中心基础设施。

FCoP 给出了另一种答案。

它使用的并不是一种新的数据库,也不是庞大的编排语言,而是开发者已经使用了几十年的东西:文件、目录、Markdown,以及少量稳定的结构关系。

文件名负责身份,结构化字段负责关系与路由,五桶负责物化生命周期状态,attempt 区分不同执行轮次,REPORT 和 REVIEW 保存交付与审查证据,而协议门禁则决定这些事实什么时候足以支持下一次迁移。

本轮实验表明,在 Windows 本地文件系统、Python 3.10.11 与 fcop==4.0.3 的受测条件下,这些关于身份、并发认领、执行轮次与授权范围的约束能够被复现。这里的结果并不代表跨机器协调、网络文件系统或大规模吞吐环境中的性能结论,但它至少说明了一件事情:工业级约束力,并不一定来自工业级复杂度。

对于多 Agent 团队而言,可靠协作的起点,也许并不是让 Agent 之间交换更多消息,而是确保任何一个 Agent 都不能仅凭自己的判断,把“我认为完成了”直接变成“系统已经完成”。

Agent 可以提出动作,FCoP 负责验证;协议通过以后,文件系统再把新的事实留下来。

这或许才是“文件系统即协议”最核心的含义:工作可以由 Agent 执行,但事实不能由 Agent 自己宣布成立。


FCoP 论文 (Zenodo) · FCoP 协议与示例 · FCoP on PyPI · FCoP MCP on PyPI · 研究仓库