WAYPOST / RFC-0001
RFC-0001:Waypost 异步信箱与记忆交换协议
正文由仓库文档生成,保留原始规范状态和示例。RFC 草案及验证计划与当前实现可能有差异;已验证范围见实现记录,加密与用户隔离升级见 安全说明。
| 项目 | 值 |
|---|---|
| 状态 | Draft / implementation baseline |
| 草案版本 | 0.1.0 |
| 应用协议版本 | 0.1 |
| 日期 | 2026-10-11 |
| 规范范围 | 身份、异步消息、独立消费、资料对象、来源记忆和可选事件 |
| 首个部署方案 | Cloudflare Workers + D1 + R2 Standard |
| 配套 | 术语、性质与验证计划、研究依据 |
1. 摘要与规范约定
Waypost 为用户、个人 Agent 和客户端提供一个可独立使用的异步信箱。生产者发布消息与记忆资料,消费者在之后读取并报告收取或处理结果。双方不必同时在线。
本 RFC 定义可以直接据以实施的 v0.1 行为。公开接口、通用 Skill 和协议格式属于同一项目;任何兼容客户端都可使用相同接口。模型执行、业务任务调度、内容理解和记忆语义合并由参与方负责。
关键词 MUST、MUST NOT、SHOULD、SHOULD NOT、MAY 按 RFC 2119 和 RFC 8174 理解。只有大写关键词表达该级别的规范要求。
本规范的操作语义优先于示例、部署建议和外部研究笔记。配套性质用于验证这些语义;发现冲突时必须先修正规范,不能靠实现静默选择。
本文是实施基线,不代表服务已运行、第三方宿主已兼容或存在官方在线地址。
2. 动机、目标与非目标
2.1 动机
个人用户可能有多个 Agent:它们分别持有会话、结果、待办线索和记忆,用户需要反复打开不同入口、搬运上下文。执行方和接收方不同时在线时,单次工具返回或临时通知不能提供连续记录。
Waypost 提供稳定的身份、持久内容与公开交换协议。用户可以保留原有助手,更换消费者或执行环境时仍可读取消息和携带记忆资料。
2.2 目标
- 已发布内容具有稳定身份,可查询、可恢复、可重复收取。
- 同一写入的重传不会形成第二次发布;内容冲突有明确结果。
- 每个消费者独立同步和确认,读取不删除其他消费者的数据。
- 来源记忆支持完整快照、增量更新、删除标记与可携带导出。
- MCP 与认证 HTTP 接口映射相同的领域操作。
- 事件通知是可选能力,持久读取始终是恢复依据。
- 权限和数据模型不依赖特定模型、宿主、应用或云平台。
2.3 非目标
v0.1 不实现 Agent 运行时托管、跨服务器联邦、自动业务编排、语义记忆合并、模型推理、团队组织系统或公开 Agent 市场。端到端加密和任意第三方平台的内部记忆抽取另行设计。
v0.1 的控制面服务一个自托管实例的所有者,可以管理多个隔离 Space。面向公众的注册、计费与多所有者账号系统不属于本 RFC,但业务接口始终执行空间隔离。
3. 设计原则与先例
| 先例 | 借鉴 | 本项目自己的边界 |
|---|---|---|
| Matrix MSC | 动机、精确操作、失败、替代方案与兼容说明 | 不采用房间、联邦或其治理流程 |
| Matrix Client-Server | 请求重传标识与增量同步位置分开 | 自己定义稳定参与者范围与独立消费者 |
| CloudEvents 1.0.2 | 可移植的事件身份和信封格式 | 身份认证、游标与记忆一致性由本 RFC 定义 |
| Standard Webhooks | 稳定事件 ID、签名与交付语义 | 通知回执不代表输入处理或业务动作完成 |
| MCP Events | 能力发现、订阅与可选通知适配 | 特定宿主的后台行为不进入通用核心 |
具体来源、核对版本和差异见研究依据。本 RFC 不宣称实现 Matrix 协议或一个已被其他宿主普遍支持的事件扩展。
4. 领域、身份与授权
4.1 数据边界
图示源码 · Mermaid
flowchart LR
A[生产者 Agent / 客户端] -->|MCP 或认证 HTTP| S[Waypost 领域服务]
S --> B[不可变正文与资料]
S --> M[消息索引 / 版本 / Feed / 回执]
M --> C[独立消费者]
B --> C
M --> E[可选事件交付适配]
所有业务对象属于一个 Space。服务生成稳定对象 ID;它们是不可解释的字符串,客户端不得依赖排序、前缀或从 ID 推导所有者。
公共 ID 字段允许 ASCII 字母、数字、_、-,长度 1–96。示例使用简短 ID;实现 SHOULD 使用具有足够唯一性的随机标识。request_id 另允许 .、:,长度 1–128,不用作文件路径。
4.2 参与者与消费者
- Actor 表示用户、Agent 或客户端身份;显示名称不是身份凭据。
- Principal 是认证得到的调用上下文:
space_id、actor_id、权限、授权代次。 - Consumer 是稳定的收取身份:所属 Actor、视图、固定过滤条件和视图版本。
- 同一 Actor 的多个安装 MAY 使用不同 Consumer;它们的回执与检查点独立。
- 令牌轮换 MUST NOT 改变 Actor、Consumer 或既有提交的幂等范围。
Space 有一个默认收件 Actor。消息省略 recipient_ids 时发给它;这个默认身份在 v0.1 中不允许原地更换。普通客户端可以自行选择接收与处理方式。
4.3 权限
| 权限 | 默认资源范围 |
|---|---|
message.send |
允许的收件 Actor 集合,缺省仅空间默认收件人 |
message.read |
当前 Actor 收到的消息与自己发布的消息 |
space.observe |
所有者显式授予的空间消息观察范围 |
receipt.write |
当前主体绑定的 Consumer |
memory.write |
当前 Actor 所有的 Collection |
memory.read |
所有者授予的 Collection 集合 |
blob.write |
当前空间、当前提交范围的暂存上传 |
event.subscribe |
主体本来可以读取的资源与过滤范围 |
生产者、Space、来源 Actor 和 Consumer 身份 MUST 来自已验证凭据。正文、MCP clientInfo、CloudEvents source 或显示名称不能扩大权限。
每次读取、写入、查询状态、附件访问和发布都重新检查当前授权。失效或已撤销凭据即使带着曾成功的幂等键,也不得取得旧结果。
撤销以元数据提交为边界,之后的新操作不能使用旧权限;已经完成发布或已发出的响应字节不能被撤回。等待上传的发布仍需在最终提交点通过授权检查。
不可读对象统一返回 NOT_FOUND,不通过错误区别“另一个空间存在”和“不存在”。已知的本空间能力不足可以返回 FORBIDDEN。
4.4 Consumer 视图
inbox 视图:读取投递给所属 Actor 的消息、向其公开的回执变化,以及有读取授权的记忆变化。
space_log 视图:需要 space.observe,观察空间中的获准消息;记忆内容仍需独立的 memory.read。观察消息不授予修改来源记忆的权限。
过滤字段限于 producer_ids、thread_ids、item_types、collection_ids,值是去重后的集合。空列表表示不额外筛选。过滤不能扩大上述视图。
视图或权限发生变化时递增 Consumer 的 view_revision。旧游标返回 CURSOR_SCOPE_CHANGED,客户端以新的起点重同步并按对象 ID 去重。
5. 版本、编码与公共结果
5.1 版本分层
| 层 | v0.1 约定 |
|---|---|
| Waypost 应用 API | 0.1;HTTP 路径 /v0 |
| 消息记录格式 | envelope_version: 1 |
| CloudEvents 格式 | specversion: "1.0",参考文本为 1.0.2 |
| MCP 版本 | 由宿主与适配器协商,不由 Waypost API 版本推导 |
| 可选宿主事件 profile | 独立公布 capability,不隐含后台执行能力 |
请求使用 UTF-8 JSON,MUST 拒绝重复 JSON 对象键、非有限数字和 schema 未声明的请求字段。用户自定义内容限于明确的 metadata 对象,不可覆盖服务字段。
同一语义请求的指纹采用 RFC 8785 JCS 后的 SHA-256。收件人集合先排序,默认值在首次保留提交时固定;附件列表、正文和 metadata 的实际内容保留。客户端时间不参与 Feed 排序。
5.2 结果格式
{
"ok": true,
"request_id": "req_001",
"data": {
"submission_state": "committed",
"resource_id": "msg_001"
}
}
{
"ok": false,
"request_id": "req_001",
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "The request ID was already used with different content.",
"retryable": false,
"details": {}
}
}
所有领域操作使用上述结果语义。MCP 适配器按实际协商的 SDK 结果格式承载它,并把失败明确标为工具失败;JSON-RPC 解析、未知方法等传输错误仍使用 MCP 规范的错误格式。
未知响应扩展字段 MAY 忽略,未知必需版本或 item 类型必须报告不支持,不能把它当作已处理并越过检查点。
5.3 默认资源限制
以下是首版的应用限制,可通过 describe 公布更小部署值;客户端必须遵循实际值。
| 资源 | 默认上限 |
|---|---|
| 普通 JSON 请求 / 消息信封 | 64 KiB UTF-8 字节 |
| 单个 Blob | 8 MiB |
| 每封消息的附件 | 16 个 |
| 收件人 | 16 个,同一 Space |
| 单页 Feed / 记忆条目 | 100 个,默认 50 |
| 单次 ACK | 20 个 item |
| 内联记忆发布 | 500 个 upsert + delete 条目 |
| manifest Blob 记忆发布 | 10,000 个 upsert + delete 条目,仍受 Blob 字节限制 |
| Collection 当前条目 | 10,000 个,包括 active items 与当前 Tombstone |
| 暂存上传期限 | 24 小时 |
大快照通过预先封存的 manifest Blob 发布,避免把全部条目装入 MCP 调用。更大的集合在 v0.1 中拆成独立 Collection,不隐藏截断。
visible_seq 在协议中为十进制字符串,避免 JSON 数值精度问题;revision 是 0 到 2^53-1 的整数。达到上限返回 RESOURCE_LIMIT,不回绕。
6. 消息契约
6.1 发送请求
{
"request_id": "req_report_001",
"recipient_ids": ["actor_user"],
"thread_id": null,
"in_reply_to": null,
"kind": "result",
"subject": "研究资料已整理",
"body": {
"text": "已整理三个方案,附件包含完整资料。"
},
"source": {
"native_thread_id": "external_thread_01"
},
"attachment_ids": [],
"metadata": {}
}
| 字段 | 规则 |
|---|---|
request_id |
必填,当前写操作的稳定幂等标识 |
recipient_ids |
可省略;显式提供时必须非空、唯一、获准且同空间 |
thread_id |
空值创建新会话;已有 ID 必须可用且当前主体有权参与 |
in_reply_to |
可空;非空时必须是可读原消息,且与 thread_id 一致 |
kind |
info、question、result、todo_suggestion、memory_notice、processing_result |
subject |
必填,非空字符串,最多 256 UTF-8 字节 |
body.text |
必填字符串,问题的选项可放 body.options,最多 20 个字符串 |
source |
可选来源引用,仅允许 native_thread_id、native_message_id、url |
attachment_ids |
封存且可用于当前投递范围的 Blob ID 列表 |
metadata |
可选自定义 JSON 对象,随信封大小限制,不参与授权 |
对回复请求,收件人 MUST 显式提供;不存在“服务根据一段自然语言猜收件人”的行为。仅提供 in_reply_to 而未提供 thread_id 时,服务使用原消息的会话。
新收件人加入已有会话,必须通过所有者的控制面授权;message.send 本身不能让新增收件人自动读取会话历史。
6.2 已发布消息
服务补齐 message_id、space_id、producer_id、thread_id、committed_at、envelope_version,以及最终固定的收件人和附件列表。committed_at 是服务发布时间,不等于来源发生时间。
消息正文与上述身份字段发布后不可变。补充、更正或撤回意图通过引用原消息的新消息表达;这不自动撤销任何外部执行。
一个消息引用的正文与全部附件 MUST 在发布前可读取并封存。单个附件未就绪时返回 BLOB_NOT_SEALED,整个消息不得部分发布。
6.3 状态与幂等
幂等键为 (space_id, producer_id, operation, request_id)。它不包含当前 token ID。指纹相同的重复调用返回原稳定结果;不同内容返回 IDEMPOTENCY_CONFLICT。
图示源码 · Mermaid
stateDiagram-v2
[*] --> reserved: 固定幂等键、指纹与资源 ID
reserved --> prepared: 正文与资料就绪
prepared --> committed: 原子发布索引与 Feed
committed --> [*]
reserved --> failed: 明确的不可重试失败
prepared --> failed: 明确的不可重试失败
网络异常本身不是 failed 事实。可重试的暂时失败保留 reserved 或 prepared,通过同一个请求恢复。并发重试不能获得两个资源 ID。
schema、权限和资料引用的前置校验失败不占用新的幂等键。保留之后被确定无法以原内容发布的失败,例如记忆 CAS 冲突或无效 manifest,记录为 failed 并返回原错误;暂时存储故障以及凭据当前不可用不能被伪装为永久业务失败。授权恢复后仍须先核实原键。
同指纹的并发请求正在执行时,可返回 SUBMISSION_PENDING 与原资源/提交标识;不返回保存成功。已发布后再次调用必须返回同一 resource_id 和 committed_at,而不是另一封“相同内容”的消息。
7. 持久发布与恢复
7.1 首个存储方案
- R2:不可变消息正文、封存 Blob、记忆 manifest 和导出资料。
- D1:身份与授权、提交保留、消息索引、Consumer、Feed、回执、Collection、Revision。
- Workers:认证、schema 校验、领域操作、MCP / HTTP 适配和可选通知。
R2 与 D1 之间没有跨服务原子事务。D1 的最终发布提交是业务可见性的线性化点。
7.2 发布算法约束
- 验证当前授权和请求格式。
- 保留幂等键、规范化指纹、稳定资源 ID 与服务时间。
- 在服务控制的不可变键准备正文或版本 manifest,核验资料引用。
- 在一笔元数据事务中完成提交状态、业务索引、版本变更和 Feed 项;发布序号在此时生成。
- 事务提交后返回
committed。如果响应丢失,同幂等键查询或重试返回第 4 步的原结果。
visible_seq MUST NOT 在上传暂存时分配;来源时间和请求创建顺序也不能替代发布顺序。
授权代次或 Consumer/Collection 状态在等待上传期间改变时,最终发布必须重新校验。重新校验与元数据写入必须防止并发撤销穿透;内存中的旧授权结果不是提交授权。
7.3 元数据原子性
每次发布要么同时写入业务记录、提交结果与 Feed,要么没有任何可见副作用。记忆 CAS 未命中时,既不能增加版本,也不能追加 Feed。
D1 适配器不得把 SQL 执行成功当作条件更新成功。必须以受同一提交条件保护的数据库操作实现 Commit;0 行命中的更新不能随后无条件插入版本或事件。具体 SQL 由实现决定,但必须通过并发和故障契约测试。
7.4 故障矩阵
| 失败点 | 可见结果 | 恢复 |
|---|---|---|
| 保留提交前失败 | 无成功结果 | 同 ID 正常重试 |
| 保留后、正文未就绪 | 未发布 | 沿原提交准备内容 |
| R2 成功、D1 失败 | 内容不可见于 Feed | 沿原提交发布或清理暂存 |
| D1 成功、响应丢失 | 已发布 | 同 ID 返回原结果 |
| Consumer 下载后崩溃、未 ACK | 内容仍在 | 重读并按 item ID 去重 |
| 收取已确认、业务处理失败 | 收取记录保留 | Consumer 从自身待处理队列恢复 |
已发布记录不依赖 Worker 内存、连接或 waitUntil 生存。暂存清理只能删除未封存、无发布引用且没有有效提交租约的资料。
8. 独立收取、游标与回执
8.1 Feed Item
Feed 项包含:item_id、space_id、visible_seq、item_type、resource_id、resource_version、producer_id、recipient_ids、thread_id、committed_at。
item_type 为 message、memory_revision 或 receipt。消息面向其收件范围;回执面向原 Feed 项的生产者和具有观察授权的主体;记忆变化仅对可读取该 Collection 的消费者可见。
每个 Feed Item 同时有不可变的类型专属 data:
| item_type | resource_id / version | data 必需字段 |
|---|---|---|
| message | message_id / 1 | message_id、subject、kind |
| memory_revision | collection_id / revision | collection_id、revision、parent_revision、manifest_blob_id |
| receipt | receipt_id / 该回执的状态版本 | subject_item_id、subject_resource_id、consumer_id、actor_id、stage、updated_at |
回执 data 描述这次推进时的阶段,不在后来读取时改成另一阶段。生产者可以据此知道哪个消费者收取了哪个输入;不必依靠缺少定义的 receipt.get。回执状态版本只在真正推进时增加。
可见性等于 Space、主体授权、Consumer 视图与固定过滤条件的交集。space_log 不自动暴露未授权记忆。
8.2 读取
{
"consumer_id": "consumer_a",
"cursor": null,
"limit": 50
}
{
"ok": true,
"request_id": "read_001",
"data": {
"items": [
{
"item_id": "feed_001",
"space_id": "space_001",
"visible_seq": "1",
"item_type": "message",
"resource_id": "msg_001",
"resource_version": 1,
"producer_id": "actor_agent_a",
"recipient_ids": ["actor_user"],
"thread_id": "thread_001",
"committed_at": "2026-10-11T10:00:00Z",
"data": {
"message_id": "msg_001",
"subject": "研究资料已整理",
"kind": "result"
}
}
],
"next_cursor": "opaque_cursor",
"has_more": false
}
}
非写入操作的响应 request_id 是服务生成的跟踪标识,不是新的幂等提交。cursor: null 从保留历史的起点开始,不能默认跳到“现在”。
读取按 visible_seq 升序、排他性位置返回。has_more 只表示响应取样时尚有符合范围的保留项,不承诺之后没有新发布。
游标是经完整性保护的 opaque 值,绑定协议、Space、Actor、Consumer、view_revision、规范化 filter 指纹与位置。客户端不得自行增减位置。游标被修改返回 INVALID_CURSOR;用于其他 Consumer 返回 CURSOR_SCOPE_CHANGED。
游标同时绑定实际 scope/resource-grants 的指纹。同 Consumer 换用权限不同的凭据时需要重新同步;同权限的令牌轮换保持游标有效,不把 token ID 当授权指纹。
每页返回的游标只能越过已返回的可见项及确定不可见的项;不能越过仍在准备但未来以较低序号补入的项。实现避免这种情况的主要规则是最终提交才分配序号。
8.3 ACK 与检查点
{
"request_id": "ack_001",
"consumer_id": "consumer_a",
"items": [
{ "item_id": "feed_001", "stage": "received" }
],
"checkpoint_cursor": "opaque_cursor"
}
received 是当前 Consumer 声明已经将输入可靠保存;processed 是其声明已处理该输入。服务不能据此证明外部动作成功。服务只验证主体、资源、Consumer、范围和回执转换。
回执按 (space_id, consumer_id, item_id) 独立保存,并单调推进 none → received → processed。乱序收到 processed 可同时建立该消费者的 received,之后迟到的 received 不能回退它。
重复同阶段 ACK 成功返回当前状态,不产生第二条回执变化事件。只有状态真正推进才发布 receipt Feed 项。确认一个回执 Feed 项本身不再产生回执 Feed 项,避免确认循环。
checkpoint_cursor 可空;非空时记录消费者声明已经可靠保存的收取位置。它不要求此前每项都 processed,也不作为其他 Consumer 的位置。
检查点按 Consumer、view_revision 与有效授权指纹分别保存。同一固定视图只能前进;视图变更后的起点是新的检查点,不拿旧视图的较大位置阻止新授权历史的补收。
客户端 MUST 在持久保存整页内容与待处理记录后提交该检查点。服务确认检查点是一个声明,不代表服务能证明客户端实际的本机持久性。
一次 ACK 的回执、检查点与相应变化记录原子提交。检查点只允许前进;较旧但合法的检查点为无副作用成功,不回退进度。
8.4 保留与重同步
v0.1 默认不自动裁剪已发布消息、版本和 Feed,不因 ACK 删除内容。初始部署保留完整增量记录,同时公布容量限额;满额时拒绝新写入,不静默丢旧信。
若部署启用了后续的保留策略,必须公布 history_floor;旧游标返回 CURSOR_EXPIRED 并提供明确重同步入口。v0.1 实现可以不提供裁剪能力,不能假装缺失历史仍可完整恢复。
重同步从保留内容重新建立视图,使用稳定 item/resource ID 去重。重同步不是执行外部操作的命令,业务副作用仍由消费者自己的去重和执行记录管理。
9. 资料上传与封存
9.1 上传协议
blob.prepare 请求:request_id、content_type、size_bytes、sha256、可选 filename。摘要是 64 位小写十六进制;大小是非负整数,受当前部署上限约束。零字节文件合法,使用空字节序列的真实 SHA-256。
响应:upload_id、blob_id、upload_url、expires_at、必须使用的请求头说明。服务生成对象键,调用方不得传入任意 bucket、路径或远程抓取地址。
v0.1 使用 PUT /v0/uploads/{upload_id}/content,原参与者凭据认证,直接流式接收文件。服务在有效上传范围内检查类型、实际字节数与 SHA-256;内容校验必须针对实际收到的字节,而非仅保存调用方声称的摘要。
Cloudflare 适配器 SHOULD 利用 R2 Put 的服务端 checksum 校验并流式写入,避免在 Worker 内缓冲或重新计算整份大文件。R2 Workers API
PUT 的成功回执说明指定字节已完整存储;它仍不是封存回执。若 R2 成功但上传 metadata 响应丢失,后续重复 PUT 或 commit 要核实实际对象,不能仅凭旧的 pending 状态认定文件不存在。
随后调用 blob.commit,携带 request_id 与 upload_id。文件校验成功且内容已可读后,原子地把 Blob 元数据标为 sealed,返回固定 blob_id、大小、类型和摘要。
commit 在原操作成功后同键重试返回原封存结果。过期规则针对尚未封存的上传;已封存资料不因原上传的 expires_at 到达而失效。
9.2 不可变性
上传对象从首次成功写入即按服务指定内容约束进行 create-only 写入;重复上传不能覆盖它。封存后新的 PUT 被拒绝,正在并发的迟到 PUT 也不能修改同键内容。
未封存上传的重复 PUT 若现存对象大小、类型和被验证的 SHA-256 与保留值一致,返回成功的 already_uploaded: true,不再次覆盖;否则返回 UPLOAD_CONTENT_CONFLICT。封存后的 PUT 返回 UPLOAD_SEALED。R2 条件写返回 null 只表示条件未满足,必须核实现存对象后选择上述结果,不能直接当作已成功写入。
暂存上传地址在本方案中不是无认证的永久桶地址。v0.1 不直接公开 R2 / S3 管理密钥,也不必引入可重复使用的预签名 PUT。以后采用这种方式时需另定义封存复制、校验、到期与撤销边界。
Blob 读取必须检查当前权限:拥有者可读取自己的封存 Blob;消息或记忆的获准读者可读取其中明确引用的 Blob。拥有读取权不自动获得向其他收件人重新引用的权利;重引用需要拥有该 Blob 或所有者明确的 blob.reference 授权。
未封存 Blob 不能进入消息或有效记忆版本。首版不执行跨 Space 的内容去重,也不暴露其他空间是否存在相同摘要。
9.3 失败与清理
摘要不符返回 CHECKSUM_MISMATCH,大小或类型不符返回 INVALID_BLOB,已过期返回 UPLOAD_EXPIRED。失败内容保持未封存,不能取得有效引用。
清理前先通过元数据条件把未封存且过期的上传标为清理中,排除有效上传/提交租约;随后才删对象。封存与清理竞争只能有一个状态转换成功。
封存 Blob 后消息发布失败,该 Blob 仍是当前来源拥有的封存资料,可用于恢复原提交。已发布引用存在时 MUST NOT 按暂存 TTL 删除它。
10. 来源记忆协议
10.1 Collection 与 Revision
Collection 属于 (space_id, source_actor_id, collection_id),并保存不可变的 scope 说明。它可以是来源原始资料,也可以是另一参与者整理的资料;服务不决定哪条自然语言“更正确”。
创建后 current_revision = 0,表示空集合。每次成功发布由服务分配 current_revision + 1;生产者只能提供 expected_revision,不能自填服务版本。
revision 0 是可读取的空状态,不要求已有 manifest Blob 或 Feed 发布。空集合的无变化提交可返回 revision 0;不会伪造 revision 1。
外部来源自己的文件版本或修改时间放在条目 metadata,不替代服务版本或 CAS。
10.2 发布请求
{
"request_id": "req_memory_001",
"collection_id": "collection_project_a",
"expected_revision": 0,
"mode": "snapshot",
"items": [
{
"item_id": "decision_001",
"blob_id": "blob_001",
"path": "decisions/product-direction.md",
"metadata": { "origin": "user_explicit" }
}
],
"delete_ids": []
}
| 字段 | 规则 |
|---|---|
request_id |
必填,稳定提交 ID |
collection_id |
必填,已创建且主体获准写入的集合 |
expected_revision |
必填,生产者基于的已确认服务版本 |
mode |
snapshot 或 delta |
items |
唯一 item ID 的条目列表,Blob 必须封存且可引用 |
delete_ids |
delta 中的明确删除集合;不得与 items ID 相交 |
manifest_blob_id |
可代替 inline items/delete_ids;两种输入互斥 |
manifest_blob_id 引用的 JSON 格式仍为 { "items": [...], "delete_ids": [...] },其实际内容也要通过相同字段、唯一性、大小和权限检查。服务不信任文件名或声称的 manifest 摘要代替内容验证。
内联输入受 500 个变更和 64 KiB 请求限制;manifest 输入最多 10,000 个变更并受 Blob 大小限制,因此支持较大的完整快照。二者的权限与状态规则相同,容量不同。发布后 active items 与当前 Tombstone 合计不得超过部署公布上限;旧 revision 的历史记录不计入当前集合条数,但计入存储容量。
10.3 Snapshot 的完整范围
Snapshot 替换该 Collection 的全部当前条目,delete_ids 必须为空。旧版存在但新版缺失的条目产生该来源内的 Tombstone。
空 snapshot 合法,表示明确清空这个集合的当前内容,必须具有写权限和正确 expected revision。通用 Skill 只有在用户授权该完整范围后才可发出;服务不猜测用户的自然语言意图。
局部同步 MUST 使用 delta。本协议没有容易产生歧义的 partial snapshot;来源仅能导出部分资料时,使用 delta 或建立明确范围的独立 Collection。
10.4 Delta 与删除
Delta 的 items 是按 item ID 的 upsert,未列出的当前条目保持不变。delete_ids 只影响同一 Collection;删除不存在的条目是无副作用操作。
当前状态由 active items 与来源 Tombstone 构成。删除现存条目时记录其 deleted_in_revision;后续无变化快照和增量保留该标记。明确 upsert 可以从当前状态移除对应 Tombstone,历史版本仍保留它。
曾删除条目以相同 ID 恢复,需要一个基于当前版本的明确 upsert。迟到的旧版本同步返回版本冲突,不能自动使它复活。
同一请求不能同时 upsert 与 delete 相同 ID。重复 item ID、路径越界或未封存 Blob 整批拒绝,不部分应用。
path 是导出资料包中的相对标识,不能是绝对路径、包含 ..、空路径段或反斜杠;不能用它直接写客户端本机文件。不同条目不得使用相同 path。
10.5 CAS、无变化与发布
先检查幂等结果,再检查该次原提交的 expected revision。重试一个已经成功的请求,即使 head 已前进,仍返回原版本;一个新的请求携带旧 expected revision,则返回 REVISION_CONFLICT。
需要发布时,准备完整、不可变的下一个状态 manifest,然后在一笔元数据事务里完成 head 的 CAS、Revision 记录、提交结果与 Feed 项。
CAS 未命中返回当前 revision,MUST 没有新 head、Revision 或 Feed 副作用。不能先在应用代码读 head,再无条件写入。
如果当前状态与请求计算结果语义相同:在 expected revision 匹配的条件下保存无变化提交结果,返回 changed: false 和原 revision,不增加版本或 Feed。metadata 变化属于内容变化;删除一个不存在的 ID 本身不构成变化。
无变化判定的保存也要在同一元数据提交点验证当前 head 仍等于 expected revision,不能在另一个写入已经前进 head 后仍返回基于旧版本的无变化成功。
{
"ok": true,
"request_id": "req_memory_001",
"data": {
"collection_id": "collection_project_a",
"revision": 1,
"changed": true,
"submission_state": "committed"
}
}
10.6 读取与导出
memory.read 读取指定 Collection 的指定 revision,或最新版本。第一次请求最新版本时返回实际 revision;之后分页必须固定该 revision,避免跨页混合两个状态。
条目按 item ID 排序,默认返回当前条目,include_tombstones 可显式获取删除标记。分页游标绑定 Collection、revision、读取范围与权限代次。
客户端可由 manifest、条目 Blob 和来源 metadata 生成可携带资料包,包括人类可读 Markdown 和结构化 JSON。服务不把连接凭证打进资料包;访问令牌、签名密钥和对象管理凭据不得作为导出元数据。
Memory read 输入为 revision(可空,表示最新)、limit、cursor 和 include_tombstones。响应必须带实际 revision、条目及 next_cursor/has_more;有游标时缺省 revision 取游标绑定值,不重新取 head。显式 revision 与游标不符返回 INVALID_CURSOR。分页固定 revision、include_tombstones 和有效读取授权。
完整导出能力为 v0.1 必需;服务端生成 ZIP 是可选便利功能,客户端自行组合的结果不得改变资料身份与来源版本。
10.7 逻辑删除与物理保留
Tombstone 表示该版本之后条目不在当前内容中,不等于立即删除旧版本或其他 Collection 的资料。
v0.1 为可恢复同步保留历史版本。彻底删除空间或资料字节属于所有者控制面的明确清理操作,必须停止访问、生成明确状态并确认清理结果;不能把一条来源 Tombstone 表述为所有参与方已经遗忘。
面向公众托管前需另行明确历史保留、备份与物理清理政策;它不改变本 RFC 的“新版本逻辑删除”和独立来源范围。
11. MCP 与 HTTP 操作映射
11.1 必需操作
| 领域操作 | MCP 工具 | HTTP |
|---|---|---|
| describe | waypost_describe |
GET /v0/describe |
| message.send | waypost_send |
POST /v0/messages |
| message.get | waypost_message_get |
GET /v0/messages/{message_id} |
| inbox.read | waypost_inbox |
POST /v0/inbox/read |
| receipt.ack | waypost_ack |
POST /v0/receipts |
| submission.status | waypost_status |
POST /v0/submissions/status |
| blob.prepare | waypost_blob_prepare |
POST /v0/uploads |
| blob.commit | waypost_blob_commit |
POST /v0/uploads/{upload_id}/commit |
| blob.get | waypost_blob_get |
GET /v0/blobs/{blob_id} |
| memory.publish | waypost_memory_publish |
POST /v0/memory/collections/{collection_id}/revisions |
| memory.read | waypost_memory_read |
POST /v0/memory/collections/{collection_id}/read |
参数来自对应章节。同一领域操作在两个传输入口具有相同授权、幂等与错误行为;幂等 operation 使用领域名称,不使用 HTTP 路径或 MCP 显示前缀。
同一个主体将超时 HTTP 提交换成 MCP 重试时,不能因此产生第二次发布。反过来也相同。
blob.get 的 MCP 结果提供鉴权下载方式与固定 Blob metadata;大内容由宿主支持的 HTTP 上传/下载能力处理,不要求模型把任意大文件装进工具上下文。宿主没有文件传输能力时,Skill 如实报告能力限制。
11.2 Describe 与状态
Describe 返回:应用版本、主体的 Space/Actor/Consumer ID、默认收件 ID、允许的收件范围、实际限制与 capability。
必需 capability:mailbox、receipts、blob_upload、memory_snapshot、memory_delta。可选 capability:webhook_events、oauth、server_export、其他宿主 profile。缺失或 false 都表示不支持,不允许客户端凭产品名称推断。
Status 输入:operation、原 request_id。它只查询当前主体的该幂等提交,返回 reserved、prepared、committed 或 failed 以及可公开的原稳定结果;未知 ID 返回 NOT_FOUND,不是“确定没执行”的跨系统证明。
11.3 认证与控制面
首版数据面 MUST 支持受限 Bearer token。Token 由服务生成至少 32 随机字节,存储摘要并绑定 Space、Actor、Consumer、scope、到期与撤销状态。令牌格式不是公开对象 ID;日志不能记录原令牌。
控制面用独立的实例管理员凭据,不将其提供给 Agent。MUST 能创建 Space、默认收件 Actor、其他 Actor、Consumer、Collection、受限令牌,并撤销令牌或主体。
控制面最小 HTTP 资源位于 /admin/v0:
| 操作 | 请求主体 | 最小结果 |
|---|---|---|
POST /spaces |
display_name | space_id、默认收件 Actor、source_uri |
POST /spaces/{space_id}/actors |
display_name、kind=user/agent/client |
actor_id |
POST /spaces/{space_id}/consumers |
actor_id、view、固定 filter | consumer_id、view_revision |
POST /spaces/{space_id}/collections |
source_actor_id、scope、display_name | collection_id、revision=0 |
POST /spaces/{space_id}/tokens |
actor_id、consumer_id、明确 scopes/resource-grants、expires_at | token_id、仅本次显示的原 token |
DELETE /tokens/{token_id} |
无 | revoked=true,幂等 |
POST /spaces/{space_id}/actors/{actor_id}/disable |
无 | disabled=true,新的发布与访问停止 |
实例管理员使用独立的 WAYPOST_ADMIN_SECRET,至少 32 随机字节,通过 Bearer 认证,不进入外部助手配置。v0.1 的控制面请求失败可由管理员查询对象后处理;资源创建 SHOULD 支持同样的 request ID 机制,防止管理客户端超时重复创建。
首个部署可以使用管理 CLI 调用受保护控制面,不必先建设完整网页账号系统。控制面路径与数据面分开;MCP 工具不能创建任意所有者或提升自身权限。
OAuth 为可选认证适配,支持后才公布 capability。只能使用 OAuth 的宿主不能被声称支持尚未实现的 Bearer 接法。面向公众托管的账号生命周期另立 RFC。
11.4 错误表
| code | HTTP | retryable | 语义 |
|---|---|---|---|
INVALID_ARGUMENT |
400 | false | schema 或语义参数无效 |
UNAUTHENTICATED |
401 | false | 凭据缺失、失效、撤销 |
FORBIDDEN |
403 | false | 已知本空间能力不足 |
NOT_FOUND |
404 | false | 不存在或无权知道该对象 |
IDEMPOTENCY_CONFLICT |
409 | false | 同键不同内容 |
REVISION_CONFLICT |
409 | false | 新请求基础版本不匹配,details 带当前 revision |
CURSOR_SCOPE_CHANGED |
409 | false | Consumer 视图或权限版本改变 |
INVALID_CURSOR |
400 | false | 游标损坏或非法 |
CURSOR_EXPIRED |
410 | false | 部署裁剪了对应历史,需重同步 |
BLOB_NOT_SEALED |
409 | false | 引用尚未封存资料 |
CHECKSUM_MISMATCH |
422 | false | 上传的实际内容摘要不符 |
INVALID_BLOB |
422 | false | 类型、大小等不符 |
UPLOAD_EXPIRED |
410 | false | 暂存上传到期 |
UPLOAD_SEALED |
409 | false | 已封存上传不能再次 PUT |
UPLOAD_CONTENT_CONFLICT |
409 | false | 同上传引用对应的现存内容不匹配 |
RESOURCE_LIMIT |
413 | false | 请求或部署容量超限,需减少内容或调整配置 |
UNSUPPORTED_VERSION |
400 | false | 不支持必需的应用版本 |
RATE_LIMITED |
429 | true | 依 retry_after_ms 有界重试 |
SUBMISSION_PENDING |
409 | true | 原提交仍在进行,查询或同键重试 |
STORAGE_UNAVAILABLE |
503 | true | 暂时不能保存或读取,不能声称成功 |
retryable 只表示同一语义请求可以再尝试,不授权客户端无限重试或改用新 ID。
11.5 版本与 schema 演进
发布前草案可以调整,但 schema、示例、Skill、客户端与测试必须一起更新。首个公开发布以 /v0 和 api_version: 0.1 为明确的实验契约。
已正式发布后,改变删除、授权、幂等范围或状态含义属于破坏性变化,必须新版本或新 RFC;不能只改工具描述。新增 capability 必须可发现且可缺省。
12. 可选事件与订阅
12.1 持久内容与通知分层
已发布 Feed Item 可以生成事件通知。通知失败、丢失、乱序或到期,都不能删除对应的消息、记忆或 Feed。
第一阶段可以只提供轮询收取;核心发布时保存足以重建事件的数据。事件适配器不得依赖某个 MCP 连接或 isolate-local 内存保存唯一事件历史。
图示源码 · Mermaid
flowchart LR
P[消息或记忆发布] --> F[持久 Feed]
F --> R[按游标收取]
F --> O[持久通知待办]
O --> W[通用 Webhook]
O --> A[可选宿主适配器]
W --> C[消费者读取实际内容]
A --> C
12.2 CloudEvents profile
事件使用 CloudEvents 1.0 structured JSON。服务为每个 Space 保存一个稳定、绝对的 source_uri,默认使用由随机 UUID 形成的 urn:uuid:...。迁移存储或域名不改变已保存的来源身份。
id 等于 Feed 的 item_id;同一事件重投保留 source + id。time 为服务发布事件的时间,序号放在 data.visible_seq,不把 time 当排序规则。
{
"specversion": "1.0",
"id": "feed_001",
"source": "urn:uuid:7fdd711b-6779-4cb8-8108-8e3df90aed95",
"type": "waypost.message.committed.v1",
"subject": "messages/msg_001",
"time": "2026-10-11T10:00:00Z",
"datacontenttype": "application/json",
"data": {
"api_version": "0.1",
"message_id": "msg_001",
"visible_seq": "1",
"thread_id": "thread_001"
}
}
初始事件类型:waypost.message.committed.v1、waypost.memory.revision.published.v1、waypost.receipt.updated.v1。
应用字段放 data,不把含下划线的字段伪装成 CloudEvents 顶层扩展。通知默认只带身份、版本、序号与资源引用,单次最多 8 KiB;完整正文通过相同授权下的读取操作获取。
事件 source 由服务确定,不能接受生产者自填它来取得身份。接收方读到事件不等于读取工具调用或外部动作获得额外权限。
12.3 订阅模型
实现 webhook_events capability 时,提供 subscription.list、subscription.upsert、subscription.stop,对应 MCP 工具 waypost_subscriptions、waypost_subscribe、waypost_unsubscribe 和 /v0/subscriptions HTTP 资源。
Upsert 输入:request_id、consumer_id、event_types、固定资源过滤条件、delivery、ttl_seconds。返回 subscription_id、expires_at、refresh_before 与已验证的交付能力。delivery 为 { mode: "webhook", url, secret };secret 由接收方预先配置,为 whsec_ 加 32 随机字节的标准 base64,不作为订阅身份的一部分。
身份由主体、Consumer、规范化过滤和交付目标组成,重复相同配置续订原订阅,不能产生重复投递源。默认 TTL 24 小时,最大 7 天;v0.1 不承诺无限期订阅。
订阅范围不能超过消费者的读取范围。每次投递前重新校验权限、期限和目标状态;停止与撤销幂等,未来投递停止。在途请求可能已经被对方接收,不能声称撤回。
订阅有效期与内容保留期分开。过期只停止提示,消费者仍用核心 Feed 补收。
12.4 通用 Webhook profile
Webhook 目标默认只接受 HTTPS 公网地址。激活前验证接收方可控制该地址,要求回调挑战成功;必须防止私有地址访问、DNS 重绑定及重定向绕过。若运行环境无法落实连接时的地址约束,该适配器不得对任意用户回调地址开放。
通用验证请求为经同样签名的 { "type": "verification", "challenge": "一次性随机字符串" }。challenge 使用 32 随机字节的 base64url,60 秒内一次有效;接收方在 10 秒内返回 2xx 和 { "challenge": "相同字符串" }。服务校验 echo 后才激活订阅,不发送业务内容试探目标。
采用 Standard Webhooks 的 HMAC-SHA256 签名,密钥为 32 个随机字节;webhook-id 使用事件 ID,webhook-timestamp 为本次签名时刻,签名覆盖实际发送的相同字节。
同一事件对不同订阅有独立投递记录;本次 attempt_id 不是事件 ID。密钥轮换使用短暂双签窗口,服务不把签名 secret 放入事件正文或日志。
接收方使用原始正文、完整签名输入与恒时比较验签,签名时间容忍度默认 300 秒;先验证再接受。签名和 ID 去重均需检查,验证请求也适用。密钥轮换的双签窗口最多 5 分钟。两端均不得解析后重新序列化原文再验签;非法签名不能取得成功交付回执。
默认最多 5 次尝试,基准延迟为初次立即、1 秒、5 秒、30 秒、5 分钟,重试加有界抖动;遵循合法 Retry-After,但不超过订阅到期。
| 响应 | 行为 |
|---|---|
| 2xx | 记录通知已接收;不推进核心收取或 processed 回执 |
| 408、429、5xx、网络异常 | 在期限与次数内重试,事件 ID 保持不变 |
| 410 | 停止该订阅,保留业务内容 |
| 413 | 永久失败,检查适配后的载荷;不盲目重试 |
| 401、403 | 暂停目标交付,等待重新验证或密钥更新 |
| 其他 4xx、3xx | 记录配置或协议错误,不跟随重定向 |
次数耗尽保存失败记录,可以人工恢复;核心收取不受影响。消费者根据 (source, id) 去重,仍不能假定 Webhook 对外动作恰好执行一次。
12.5 宿主适配器
通用业务 API、Feed 和 CloudEvents profile 不依赖 OpenAI 账号、SDK、callback 地址或其后台执行环境。
可选 ChatGPT MCP Events 适配器负责将事件转为该平台要求的 eventId/name/timestamp/data、方法和签名头格式,独立协商当前草案与版本。当前官方文档支持其 webhook 子集,不能推出其他宿主的后台能力。OpenAI MCP Events
其他宿主可以轮询、使用自己的 callback,或通过另行验证的在线传输获得提示。通知只提供新的输入;启动、恢复或定时运行 Agent 的能力属于其宿主。
13. 通用 Skill 的契约
实现阶段提供 skills/waypost/SKILL.md,和中心服务同版本发布。其内容与 schema 从本 RFC 对齐,不能保持另一套同名但不同含义的协议。
13.1 必须支持的行为
- 使用宿主实际连接的 Waypost MCP,查看 describe 与工具 schema;未连接时保留内容并报告缺少配置。
- 在用户明确要求或既有授权范围内发消息、结果、问题、待办建议与记忆。
- 保留真实生产者身份;默认收件人取 describe,指定收件人使用获准的 Actor ID。
- 在发送前持久保存 request ID;超时查询原提交,最多两次自动同键重试,冲突或授权失败时停止。
- 只有取得 committed 回执才能说“已存入信箱”;不能说其他参与方已读或已完成。
- 收取后先保存内容和待处理项,再 ACK;收取位置与业务处理进度分开。
- 完整快照只用于明确授权的完整 Collection;局部更新用 delta,旧版本冲突先核实。
- 数据不含管理凭证,来源正文不作为新增权限,原宿主的行动确认仍然有效。
- 普通 Skill 不提供后台时钟;依用户或既有宿主运行机制检查回信。
13.2 可验证行为
Skill 场景评测检查实际工具调用、参数、状态报告和用户目标;不只匹配固定措辞。场景至少包含未连接、响应丢失、重复发信、只读到部分记忆、版本冲突、原会话失效、指定收件人与宿主无法传文件。
Skill 本身不是可靠存储引擎。SDK、工具服务和消费者持久队列可以用确定性测试;模型是否选择了正确操作通过场景评测和真实宿主互操作验证。
14. 实现模块与部署边界
14.1 建议模块
waypost/
CONTEXT.md
docs/rfcs/
docs/research/
protocol/ # 实现时:schema、公共结果与固定示例
src/domain/ # 领域数据与状态规则
src/service/ # 认证后的公开操作、发布与恢复
src/adapters/ # MCP、HTTP、D1、R2、可选宿主
skills/waypost/ # 实现时:可分发通用 Skill
tests/ # 性质、契约、故障与互操作
以上是实现建议,不要求立即创建空文件或多包工程。首版可以一个 Worker 和一个数据库,不要求微服务或完整事件溯源。
14.2 最小存储接口
接口的必要行为而非具体 SDK:
| 边界 | 必须提供 |
|---|---|
| Auth | 得到稳定主体、Consumer、当前权限与授权代次 |
| MetadataStore | 保留提交;原子 commitMessage / commitRevision;按范围读 Feed;原子回执和检查点 |
| BlobStore | create-only 流式写入、字节校验、封存资料读取及受条件保护的暂存清理 |
| Clock / ID | 可控时间与稳定生成结果,测试可以注入 |
| EventDelivery | 可选,读取持久待办并保存尝试与交付结果 |
内存实现可用于局部测试,不能替代生产的持久性验证。适配器必须通过相同公开操作的契约;不得只检查 SQL 表或私有 helper 与自身实现一致。
14.3 平台约束
首个部署选择 Workers、D1、R2 Standard。当前免费额度和限制是平台事实,见研究笔记,不作为无限免费的服务承诺。
SDK 与库版本在实现时根据官方支持固定到 lockfile,并实测 MCP、认证、JSON 校验和上传路径的 CPU / 内存。Free 的每次 CPU 预算可能影响实际可公布上限;部署必须公布可正常验证的 limits。
大型资料不走 D1 单行,分页查询有相应索引,原信不以一个可覆盖的大 JSON 保存。Queues 只能作短期交付提示;资料与 Feed 持久保留。无需为基础 MCP 引入常驻 Agent 或 Durable Objects。
15. 性质与验证边界
完整定义、前置条件、随机操作与反例见 RFC-0001 性质计划。首批性质:
| ID | 承诺 |
|---|---|
| P01 | 跨 Space 与未授予资源不可读写 |
| P02 | 同主体、同操作、同请求、同内容只有一个稳定发布结果 |
| P03 | 同幂等键不同内容没有覆盖或第二次发布 |
| P04 | 保存成功后内容可读且重启可恢复 |
| P05 | 未最终发布内容不进入 Feed |
| P06 | 较晚完成的提交不因较早保留序号而漏收 |
| P07 | Consumer 的检查点与回执彼此独立 |
| P08 | 回执单调,重复 ACK 没有重复变化事件 |
| P09 | 同一稳定视图分页完整,篡改或跨视图游标被拒绝 |
| P10 | 记忆 CAS 冲突没有 head、Revision 或 Feed 副作用 |
| P11 | Snapshot 替换完整集合,Delta 的遗漏不表达删除 |
| P12 | 来源删除不影响其他 Collection,旧同步不能自动复活 |
| P13 | 未封存或被修改资料不能进入已发布记录 |
| P14 | 令牌轮换保留幂等身份,撤销后旧键也不能读取结果 |
| P15 | Webhook 成败不改变核心业务记录,事件重投保持身份 |
| P16 | 同领域操作跨 MCP / HTTP 重试仍只有一个发布 |
| P17 | 收取后未处理的输入在 Consumer 恢复后仍可接续 |
| P18 | 允许的保留与授权条件成立时,恢复后可以补收全部已发布可见项 |
P01–P16 主要是安全性 / 不变量;P17 涉及客户端持久队列契约;P18 是带恢复与保留前置条件的活性目标。随机测试提高发现反例的能力,不构成对所有网络和故障的数学证明。
16. 关键端到端与反例场景
16.1 首个异步闭环
图示源码 · Mermaid
sequenceDiagram
participant A as Agent A
participant W as Waypost
participant C as Client B
A->>W: send(request_id, 内容)
W->>W: 准备正文,最终发布 Feed
W-->>A: committed
Note over A,C: 两方可以先后上线
C->>W: inbox(cursor)
W-->>C: 稳定 item 与正文引用
C->>C: 保存原内容和待处理项
C->>W: ACK received 与 checkpoint
C->>W: send(回信,引用原消息)
W-->>C: committed
A->>W: 下次读取自己的 inbox
W-->>A: 回信
A->>W: 保存后 ACK
16.2 必须保留的游标反例
保留 A → 发布 B → Consumer 读到 B → A 完成发布 → Consumer 继续读
A 必须拥有晚于 B 的最终发布序号并被后续读到。若实现先为 A 分配可见序号,它可能被 Consumer 已越过的游标漏掉,这是不合规实现。
16.3 并发记忆反例
两次不同 request 都基于 revision 3 发布不同内容;它们并发等待 Blob 后进入提交。
最多一个成功成为 revision 4,另一个返回 revision conflict。失败者不能产生 revision 5、幽灵 Feed 或隐藏覆盖。若它后来基于 revision 4 进行真实合并,应使用新的请求并提供 expected revision 4。
16.4 独立消费者反例
两个有权读取同一消息的 Consumer A、B。A 收取并 ACK 后,B 仍能从自己的起点读取;A 的 processed 不能标记 B 已处理。B 收到的原消息与 A 相同。
16.5 局部记忆反例
Collection 有 a、b。来源只能读到 a 时,发布更新 a 的 delta;b 保持。只有明确完整 snapshot 或明确 delete 才能删除来源内的 b。
17. 实施阶段与验收
M0:最小可靠消息闭环
实现受限认证与控制面、describe、send、message.get、inbox、ACK、status;先本地模型和测试,再 D1/R2 真实适配。
门槛:P01–P09、P14、P16–P18 相关性质通过,存储故障点测试通过,两个独立消费身份完成离线往返。不能用 mock 成功代替实际发布验证。
M1:资料、记忆与通用 Skill
实现 Blob 上传封存、snapshot / delta / read / 导出资料,补齐 P10–P13。发布通用 Skill,并以真实兼容宿主完成消息和记忆流程。
门槛:普通消息、完整快照、局部 delta、冲突、空 snapshot、重复提交、令牌轮换、拒绝未授权资料等契约成立;消费者可恢复尚未处理的输入。实际免费部署的容量与 CPU 经过测量。
M2:可选通知
在核心内容稳定后增加通用 Webhook、订阅持久化和 P15。只有验证任意回调的安全访问条件后开放;随后按需求增加宿主适配,不要求所有 MCP 客户端支持同一扩展。
RFC 实现完成条件
- M0 与 M1 全部必需能力、性质、错误和独立客户端验收完成。
- 未实现的可选功能如实公布为不支持。
- schema、服务、Skill 与示例保持同版,测试可以从失败种子重现。
- 没有未解决的权限、发布、游标或记忆 CAS 语义冲突。
- README 区分本地验证、云端验证和真实宿主验证的范围。
18. 替代方案与选择理由
| 方案 | 判断 |
|---|---|
| 只把 JSON 写进对象桶,客户端反复列桶 | 初期容易演示,但身份、可见发布、游标与多消费者状态缺乏统一契约;本方案使用独立元数据索引 |
| 全部内容存数据库 | 部署简单,但大文件与原始记忆影响行大小和容量;正文与资料采用 BlobStore |
| 强制长期 WebSocket 或宿主事件扩展 | 增加接入限制,离线恢复仍需要持久内容;本方案保留 pull 基础 |
| 每次读取后删除队列消息 | 不适合独立消费者和迁移;收取确认不删除原记录 |
| 以 token ID 定义提交身份 | 轮换会破坏幂等连续性;使用稳定 Actor |
| 部分 snapshot 隐含保留或删除 | 难判定来源意图;完整 snapshot 与 delta 分开 |
| 服务负责判断任务完成和合并偏好 | 将平台语义和模型判断放进通用通信核心;由参与方负责,服务记录事实 |
| 首版采用多服务、联邦或全面事件溯源 | 超出当前验证目标;先单实例和明确公共操作 |
19. 安全、运维与待决策事项
19.1 安全边界
服务验证鉴权、收件与资料范围;客户端验证来源数据与渲染方式。外部消息和记忆不是 system instruction,也不是用户授权凭证。
桶保持私有;所有读取经当前授权校验;日志记录资源 ID 与失败类型,不记录原凭据、签名密钥或默认完整记忆内容。跨空间数据和存在性不通过摘要去重或错误细节泄漏。
动作请求与动作结果都可以作为消息,但发送“允许执行”不绕过宿主原有权限。Waypost 无权替接收方保证外部副作用恰好一次。
19.2 可观察性与恢复
观察提交积压、准备但未发布资料、已发布但未收取项、Consumer 检查点、记忆冲突、回调失败与容量;报告准确阶段,不用单个“成功率”混合保存和处理。
元数据与 Blob 都需要备份与恢复验证。只恢复 D1 而缺少所引用 Blob 不符合 P04;不匹配备份应报告不可读资源并停止声称完整恢复。
19.3 尚未定稿,但不阻塞 M0/M1 的选择
开源许可证、官方托管注册与计费、公众账号恢复、物理删除与备份保留政策、端到端加密、超大集合、客户端 UI 与后续宿主接法,均可另写 RFC。
这些项不能被实现者静默扩张成新协议。需要改变已定义语义时,修订本 RFC 或提出后续 RFC。
20. 参考资料
资料核对:2026-10-11。版本和正文事实见研究笔记。下面的来源指导本项目的选择,不直接替代 Waypost 的契约。
- RFC 2119、RFC 8174:规范性关键词。
- RFC 8785:JSON canonicalization。
- Matrix MSC README、MSC Checklist:可实施提案写法。
- Matrix v1.19 Client-Server:同步与请求重传先例。
- CloudEvents v1.0.2:事件格式与稳定身份。
- Standard Webhooks:签名与通知交付。
- Cloudflare MCP handler:首个 MCP 部署方式。
- D1 API、R2 API:存储适配边界。
- OpenAI MCP Events:可选宿主事件适配参考。
- fast-check model-based testing:性质测试方法。