Publish portable agent state market research
This commit is contained in:
@@ -0,0 +1,286 @@
|
|||||||
|
---
|
||||||
|
{
|
||||||
|
"title": "可移植 Agent 状态:开源市场、技术可行性与商业机会",
|
||||||
|
"summary": "审计 MCP、A2A、AG-UI、Agent File、Agent Spec、Agent Format、memorywire、Solid、AT Protocol、Automerge 与 UCAN,并用双 adapter 实验判断跨 harness 用户状态层是否值得投入。",
|
||||||
|
"date": "2026-08-22",
|
||||||
|
"updated": "2026-08-22",
|
||||||
|
"topic": "agent-systems",
|
||||||
|
"tags": ["agent-state", "interoperability", "local-first", "open-source", "protocol", "commercialization"],
|
||||||
|
"kind": "article",
|
||||||
|
"status": "published",
|
||||||
|
"visibility": "public",
|
||||||
|
"canonicalUrl": "https://blog.k1412.top/articles/portable-agent-state-open-market/",
|
||||||
|
"sourceRepo": "https://git.k1412.top/wuyang/research-blog",
|
||||||
|
"preview": "/articles/portable-agent-state-open-market/portable-agent-state-cover.png"
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
> **结论:技术成立,市场也存在,但“再发明一个 Agent 状态格式”已经不是好机会。更值得下注的是兼容层:把 Agent File、Agent Spec、Agent Format、Agent Protocol 与各类 memory backend 接起来,提供明确的有损迁移报告、授权撤销、一致性测试和企业治理。**
|
||||||
|
|
||||||
|
**证据截止:2026-08-22|阅读时间:约 24 分钟|结论性质:公开规范与仓库审计 + 本次受控原型实验,不是投资建议**
|
||||||
|
|
||||||
|
**证据级别:E1 产品声明或定价|E2 官方规范、文档、论文或源码|E3 公开互操作工件|E4 本次受控运行**
|
||||||
|
|
||||||
|
## 一、最重要的判断
|
||||||
|
|
||||||
|
1. **方向不是伪命题。** 用户偏好、长期 memory、任务 checkpoint、来源、授权与撤销完全可以放到 harness 之外,由多个运行时读写。Solid、AT Protocol、Automerge、DID 与 UCAN 已经把存储、签名、同步和能力授权的大部分难题拆开解决。
|
||||||
|
|
||||||
|
2. **方向也不是无人区。** 到 2026 年,至少已有 Letta Agent File、Oracle Agent Spec、Snap 发起的 Agent Format、LangChain Agent Protocol、memorywire 和 Portable Agent Memory 六条相邻路线。它们分别搬运 Agent 定义、运行状态或 memory,但尚未形成一个被多个独立 harness 共同实现、同时覆盖所有权、撤销和有损声明的稳定公共层。
|
||||||
|
|
||||||
|
3. **最大技术风险不是 CRDT,也不是区块链。** 本次 E4 实验中,签名事件、乱序合并、篡改检测和撤销都很直接;真正不能自动解决的是语义等价。例如 Letta 的 editable block、某个 harness 的 tool rule、另一个 runtime 的 graph checkpoint,并没有天然的一一映射。
|
||||||
|
|
||||||
|
4. **商业价值已经被相邻市场验证,但收入在服务层。** Letta、Mem0 和 LangSmith 对 state/memory/runtime 按 Agent、请求、计算、存储或席位收费;Inrupt、Authzed、Element 和 Electric 则证明开放协议或开源核心可以在托管、企业治理、SLA、私有部署和支持上收费。没有证据表明“收协议税”是必要条件。
|
||||||
|
|
||||||
|
5. **如果要做类似比特币的长期技术押注,应押网络效应,不押代币。** 这里的网络效应来自 adapter 数量、迁移测试语料、conformance badge 和被更多 harness 默认支持;代币与全局链既不是技术必需,在北京开展相关业务还会平白增加监管风险。
|
||||||
|
|
||||||
|
## 二、先定义对象:什么状态应该归用户
|
||||||
|
|
||||||
|
Agent harness 是执行外壳:它装配模型、工具、权限、会话、事件循环和 UI。此前对 [DeepSeek Harness 的代码审计](/articles/deepseek-harness-architecture-evaluation/)显示,session event、tool runtime 与插件状态在 harness 内部都很合理。问题只在于:换一个 harness 后,哪些东西仍应跟着用户走?
|
||||||
|
|
||||||
|
一个最小的跨 harness 状态层应包含六类对象:
|
||||||
|
|
||||||
|
| 状态类别 | 典型内容 | 迁移要求 |
|
||||||
|
|---|---|---|
|
||||||
|
| 用户画像与偏好 | 语言、输出风格、常用设置 | 可选择披露,可覆盖、可删除 |
|
||||||
|
| 长期 memory | 事实、事件、程序性经验 | 带来源、置信度、生命周期 |
|
||||||
|
| 工作状态 | 任务、checkpoint、待确认动作 | 能恢复,但不承诺跨 runtime 逐指令续跑 |
|
||||||
|
| 策略与授权 | 哪个设备或 Agent 能读写什么 | 可衰减、可过期、可撤销 |
|
||||||
|
| 来源与审计 | 谁在何时基于什么写入 | 可验证,支持追责与清理 poisoned memory |
|
||||||
|
| artifact 引用 | 文件、数据集、生成物 | 内容寻址,存储位置可替换 |
|
||||||
|
|
||||||
|
四类内容不应默认塞进可移植包:模型权重、API secret、未授权的原始对话,以及模型的隐藏推理过程。特别是 secret 应留在设备或企业 vault 中,迁移包只携带引用和所需权限。
|
||||||
|
|
||||||
|
“可移植”也必须拆成三个等级:
|
||||||
|
|
||||||
|
- **语法可读**:另一个实现能解析文件。
|
||||||
|
- **状态可用**:目标 runtime 能导入其中一部分并继续工作。
|
||||||
|
- **行为等价**:同样输入在不同模型、工具和调度器上产生相同效果。
|
||||||
|
|
||||||
|
前两项可以工程化;第三项通常做不到。一个诚实的协议必须输出 `loss manifest`,逐项说明哪些字段完整映射、降级映射、被拒绝或只作 opaque 保存,而不是用“无缝迁移”掩盖差异。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
_图 1|建议架构:已有协议负责调用和通信;新工作的核心是可移植语义、损失声明与一致性测试。_
|
||||||
|
|
||||||
|
## 三、开源市场不是空白,而是一组尚未拼好的层
|
||||||
|
|
||||||
|
### 3.1 MCP、A2A 与 AG-UI 解决相邻问题
|
||||||
|
|
||||||
|
截至截止日,MCP `2026-07-28` 已把核心改为无 handshake、无 session 的无状态协议,并强化扩展和 OAuth 授权。这反而明确了边界:**MCP 负责客户端如何调用工具与资源,不保存用户跨应用的长期状态。** 状态服务可以暴露成 MCP extension 或 tool,但状态所有权、合并和迁移仍需另一份契约。[MCP 2026-07-28 发布说明](https://blog.modelcontextprotocol.io/posts/2026-07-28/) [MCP 授权](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
|
||||||
|
|
||||||
|
A2A `1.0.0`面向独立且可能不透明的 Agent,核心对象是 Message、Task 和 Artifact;它让 Agent 相互委托工作,不要求它们共享内部 memory。[A2A 1.0 规范](https://a2a-protocol.org/v1.0.0/specification) [A2A 1.0.1 release](https://github.com/a2aproject/A2A/releases/tag/v1.0.1)
|
||||||
|
|
||||||
|
AG-UI 标准化 Agent backend 与用户界面之间的双向事件,包含 state snapshot/update,所以它非常适合**投影当前状态**;但官方介绍没有定义该状态归谁、如何跨供应商迁移、如何恢复密钥或传播撤销。[AG-UI 官方介绍](https://github.com/ag-ui-protocol/ag-ui/blob/main/docs/introduction.mdx) [AG-UI releases](https://github.com/ag-ui-protocol/ag-ui/releases)
|
||||||
|
|
||||||
|
这三者都应复用,但不能把它们误称为用户状态层。
|
||||||
|
|
||||||
|
### 3.2 真正的直接竞争者已经出现
|
||||||
|
|
||||||
|
| 方案 | 它能搬什么 | 截止日成熟度信号 | 明确缺口 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| [Agent File `.af`](https://github.com/letta-ai/agent-file/tree/78212eb571e59e10b35b924375a997319b89c280) | system prompt、消息、memory block、tool、模型设置 | Apache-2.0;GitHub 页面约 1.2k stars;真实文件可导入 Letta | README 明说跨框架“理论可行”但要自行映射;archival passages、schema migration、converter 仍在 roadmap |
|
||||||
|
| [Oracle Agent Spec](https://github.com/oracle/agent-spec/tree/c9b0a20f1a5d09aecb081c49e760ee2f77325c76) | Agent、tool、workflow 的声明式定义 | Apache-2.0;本次审计 928 个文件、301 个 test/spec 路径,并有多框架 adapter | 更接近“定义一次、多处执行”;不解决用户长期状态的双向同步与所有权 |
|
||||||
|
| [Agent Format](https://agentformat.org/) | identity、interface、tool、constraints、memory requirement、execution policy | Snap 发起;Apache-2.0;网站提出 conformance program | 截止日 schema 仓库只有 4 个文件、5 次提交,GitHub 页面显示 22 stars;治理和 SDK 宣称早于仓库成熟度 |
|
||||||
|
| [LangChain Agent Protocol](https://langchain-ai.github.io/agent-protocol/) | run、thread revision、store CRUD、state stream | OpenAPI/JSON/CDDL;LangGraph Platform 实现其超集 | 服务端 API 很完整,但用户自持身份、provider migration 与跨实现撤销不是中心目标 |
|
||||||
|
| [memorywire](https://github.com/mthamil107/memorywire/tree/b4bff073eb3aaeae663bf39a126da0ed15e70c33) | remember、recall、forget、merge、expire;四类 memory | 五个 backend adapter;论文报告 68 PASS / 12 SKIP / 0 FAIL | v0 draft、单作者预印本;仓库页面显示 0 stars,README 写明尚未发布 PyPI;只覆盖 memory |
|
||||||
|
| [Portable Agent Memory](https://arxiv.org/abs/2605.11032) | 五类 memory、Merkle-DAG 来源、capability disclosure、rehydration | 单作者 v1 预印本宣称 54 tests 与四类模型迁移 | arXiv 页面没有代码链接,本次无法独立复现;不覆盖完整 task/tool/runtime state |
|
||||||
|
|
||||||
|
最接近证伪本文主张的是 Agent File:它确实试图“把有状态 Agent 放进一个文件”。但它自己的 README 同时给出证据边界:其他框架必须把 Letta 概念翻译成自己的表示,部分 block 语义不存在,converter 尚未完成。也就是说,**格式已经出现,跨实现的兼容责任仍然空缺。**
|
||||||
|
|
||||||
|
### 3.3 下层部件已经足够,不必从零造“新互联网”
|
||||||
|
|
||||||
|
| 可复用部件 | 已有方案 | 能直接贡献什么 | 不能替代什么 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 用户外置数据 | [Solid 0.11](https://solidproject.org/TR/protocol) | HTTP storage、WebID、OIDC、WAC/ACP;应用与数据存储分离 | Agent state ontology;且它仍是 Community Group Report,不是 W3C Standard |
|
||||||
|
| 签名仓库与托管迁移 | [AT Protocol repository](https://atproto.com/specs/repository) / [account](https://atproto.com/specs/account) | 内容寻址 Merkle tree、签名 commit、CAR 导出、PDS migration | repo 内容默认公开,不适合直接承载私密 Agent memory |
|
||||||
|
| 去中心化事件 | [Nostr NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) | 极简签名 event + relay 模型 | 复杂授权、统一删除语义、强一致恢复 |
|
||||||
|
| 本地优先合并 | [Automerge](https://automerge.org/docs/reference/concepts/) | JSON-like CRDT、紧凑历史、传输无关 sync、storage/network adapter | 身份、授权、业务语义 |
|
||||||
|
| 用户发起的能力授权 | [UCAN 1.0](https://ucan.xyz/specification/) | DID principal、delegation、attenuation、invocation、revocation | key recovery、撤销传播服务、企业策略 UI |
|
||||||
|
| 可验证标识 | [W3C DID Core](https://www.w3.org/TR/did-core/) | 与单一 registry/IdP 解耦的标识和 verification method | DID 本身不提供信任、声誉或安全的 key recovery |
|
||||||
|
|
||||||
|
这里最容易犯的错是“既然用了签名、Merkle DAG 和去中心化标识,就上链”。DID Core 明确允许底层 registry 是 ledger、去中心化文件系统、数据库或 P2P 网络;本次需求不需要全局排序、稀缺资产或抗双花,因此本地事件日志 + 可替换同步节点已经足够。[DID Core 架构](https://www.w3.org/TR/did-core/#architecture-overview)
|
||||||
|
|
||||||
|
## 四、论文支持什么,又没有支持什么
|
||||||
|
|
||||||
|
### 4.1 已有研究支持“数据优先于云端应用”
|
||||||
|
|
||||||
|
Ink & Switch 的《[Local-First Software](https://www.inkandswitch.com/essay/local-first/local-first.pdf)》把 CRDT 视为用户拥有数据、离线工作与多人协作的关键基础。Automerge 随后的 [sync protocol 论文](https://arxiv.org/abs/2012.00472)进一步说明,复制端可以只交换缺失变更,而不依赖中心服务器。AT Protocol 使用的 [Merkle Search Tree](https://doi.org/10.1109/SRDS.2019.00032)则表明,内容寻址树可以支持开放网络中的可验证同步。
|
||||||
|
|
||||||
|
这些研究足以支持“状态可外置并同步”,却没有定义 Agent memory 是什么,也没有解决权限撤销和 prompt injection。
|
||||||
|
|
||||||
|
### 4.2 Agent portability 研究正在形成,但证据等级仍低
|
||||||
|
|
||||||
|
Oracle 的 [Agent Spec 技术报告](https://arxiv.org/abs/2510.04173)聚焦 Agent 与 workflow 的框架无关定义;2026 年的 [Portable Agent Memory](https://arxiv.org/abs/2605.11032)和 [memorywire](https://arxiv.org/abs/2606.01138)开始直接研究 memory 搬运、来源与 adapter。后两篇都还是单作者 arXiv 预印本,应该视为**设计提案与可行性信号**,不能视为多团队互操作已经成立。
|
||||||
|
|
||||||
|
安全研究也在提醒不要低估难度。[Keyhive](https://www.inkandswitch.com/keyhive/notebook/)仍把 local-first access control 描述为探索项目;2026 年的[本地优先访问控制形式化验证工作](https://arxiv.org/abs/2604.23560)证明形式化方法有潜力,同时明确把扩展到 Matrix、Keyhive 级真实复杂度留作未来工作。
|
||||||
|
|
||||||
|
因此,论文给出的合理结论是:
|
||||||
|
|
||||||
|
- 数据结构、同步和签名都有成熟研究基础;
|
||||||
|
- Agent 状态语义与跨框架 rehydration 刚进入原型期;
|
||||||
|
- 安全与一致性测试必须成为产品本体,不能是发布前补丁;
|
||||||
|
- “一篇论文 + 一个 SDK”不等于协议网络已经形成。
|
||||||
|
|
||||||
|
## 五、最小可行技术架构
|
||||||
|
|
||||||
|
一个务实的 v0 不应统一所有 Agent,而应规定五个窄接口:
|
||||||
|
|
||||||
|
1. **Identity**:用户根身份与设备身份分离。设备各自持有 key,用户通过短期 capability 授权;不要在设备之间复制私钥。
|
||||||
|
2. **Signed event**:每次状态变更都带 writer、clock、parent、schema、payload hash 和 signature;事件 ID 由 canonical bytes 的 hash 得出。
|
||||||
|
3. **State namespace**:先只标准化 `profile.*`、`memory.*`、`task.*`、`grant.*`、`artifact.*`。框架专属状态放进带 vendor namespace 的 opaque block。
|
||||||
|
4. **Capability**:用 UCAN 类语义表达 read/write/export/forget,支持 expiry、attenuation 和 irreversible revocation;MCP OAuth 只负责访问某个 server,不能代替用户层 capability。
|
||||||
|
5. **Adapter report**:每次 export/import 必须输出完整映射、降级映射、未支持字段、secret omission 和建议人工动作。
|
||||||
|
|
||||||
|
同步可以分两条:审计和授权使用 append-only signed event;用户可编辑的 map/list 使用 Automerge 一类 CRDT。大 artifact 只存内容 hash,实际数据可以在本地、Solid Pod、S3 兼容对象存储或企业存储中。同步服务不是权威,任意节点都能校验事件和重建状态。
|
||||||
|
|
||||||
|
这个架构与 DeepSeek、OpenAI-compatible API 一类“兼容优先”路线的经验一致:开发者采用新基础设施,通常不是因为它先赢得了抽象定义之争,而是因为现有客户端只改少量配置就能工作。DeepSeek 官方文档允许兼容软件更换 base URL 接入 API;其 [R1 仓库](https://github.com/deepseek-ai/DeepSeek-R1)又以开放权重扩大分发。但**开放权重、API 兼容与开放状态协议是三件不同的事**。[DeepSeek API 文档](https://api-docs.deepseek.com/)
|
||||||
|
|
||||||
|
## 六、本次 E4 实验:容易的是可验证搬运,难的是语义等价
|
||||||
|
|
||||||
|
本次用 Node.js v26.5.0 写了一个不依赖第三方库的最小 probe。Harness A 的内部结构是 `user.preferences + memories[] + task + tool_rules + secrets`;Harness B 则是 `profile + recollections + work + vault`。二者通过一个 canonical signed-event log 交换状态。
|
||||||
|
|
||||||
|
实验步骤:
|
||||||
|
|
||||||
|
1. 用户根 key 分别向 A、B 发出有限期 `state.put` grant。
|
||||||
|
2. A 导出语言、回答风格、一条 memory 和当前任务。
|
||||||
|
3. B 导入为自己的内部结构。
|
||||||
|
4. A、B 并发修改同一路径,分别以不同顺序合并事件。
|
||||||
|
5. 篡改一个已签名事件,检查拒绝。
|
||||||
|
6. 撤销 B 的 grant,再发送一次写入,检查拒绝。
|
||||||
|
7. 搜索导出结果,确认 secret 未出现。
|
||||||
|
8. 对无法等价表达的 `tool_rules` 生成显式 loss item。
|
||||||
|
|
||||||
|
结果为 **10/10 assertions 通过**;两种到达顺序得到相同 state hash `d96f9b…35688`;篡改事件和撤销后的写入各被拒绝一次;真实 Letta `.af` 样本的静态审计发现 1 个 Agent、9 个 block、9 个 tool、3 条 message,导出 secret 值为空。
|
||||||
|
|
||||||
|
| 已证明 | 未证明 |
|
||||||
|
|---|---|
|
||||||
|
| 偏好、memory、task 可跨两种内部结构搬运 | 不同模型与工具下的行为等价 |
|
||||||
|
| Ed25519 签名与 content hash 可发现篡改 | 大规模同步吞吐与存储成本 |
|
||||||
|
| 事件乱序仍得到确定性状态 | 设备丢失后的 key recovery |
|
||||||
|
| capability 撤销能拒绝后续写入 | 多用户、多组织策略组合 |
|
||||||
|
| secret omission 与 loss report 可自动检查 | 真实 LangGraph/Letta/DeepSeek 三方互操作 |
|
||||||
|
|
||||||
|
可下载 [实验源码](/articles/portable-agent-state-open-market/portable-state-probe.mjs)与[结构化结果](/articles/portable-agent-state-open-market/portable-state-probe-result.json)。实验只证明最小机制可实现,不证明生产系统已经安全。
|
||||||
|
|
||||||
|
### 可行性评分
|
||||||
|
|
||||||
|
| 维度 | 判断 | 理由 |
|
||||||
|
|---|---:|---|
|
||||||
|
| 数据格式与签名 | 4.5 / 5 | 标准部件成熟,原型直接 |
|
||||||
|
| 离线同步与合并 | 4 / 5 | CRDT/event log 有成熟实现;业务冲突仍需规则 |
|
||||||
|
| 授权与撤销 | 3 / 5 | capability 可行;传播、缓存失效和恢复复杂 |
|
||||||
|
| 跨 harness 语义 | 2 / 5 | 最难;必须用 adapter + loss manifest,而非假装等价 |
|
||||||
|
| 开源治理与标准化 | 2 / 5 | 已有多条竞争路线,名字和 schema 本身不稀缺 |
|
||||||
|
| 做成付费兼容产品 | 3.5 / 5 | 有相邻付费信号;需要真实设计伙伴验证 |
|
||||||
|
|
||||||
|
**所以:做研究原型的可行性高;孤立发布新协议的胜率低;以迁移和治理为楔子的产品可行性中等偏高。**
|
||||||
|
|
||||||
|
## 七、商业机会:钱在协议上方
|
||||||
|
|
||||||
|
公开定价不能证明收入规模,但能证明客户已经习惯为什么付费:
|
||||||
|
|
||||||
|
| 相邻产品 | 开放底座 | 截止日公开收费信号 | 对本方向的启示 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| [Letta](https://docs.letta.com/pricing) | Stateful Agent / Agent File | API plan $20/月;$0.10/active agent/月;tool execution $0.00015/秒;Enterprise 提供 RBAC、SSO、支持 | Agent 状态可按活跃对象与执行量计费 |
|
||||||
|
| [Mem0](https://mem0.ai/pricing) | 开源 memory engine | Starter $19/月,Pro $249/月;Enterprise 提供 on-prem、audit log、SSO | memory 本身红海,企业控制面更值钱 |
|
||||||
|
| [LangSmith](https://www.langchain.com/pricing) | LangGraph / Agent Protocol 相邻生态 | Plus $39/seat/月;按 compute/storage unit 计费;Enterprise 支持 hybrid/self-hosted | durable runtime、state 与治理可组合收费 |
|
||||||
|
| [Electric](https://electric.ax/pricing) | Apache-2.0 sync | $1/百万次写入、$0.10/GB·月;Pro $249/月;Scale $1,999/月 | 开放同步协议可按写入、留存、SLA 收费 |
|
||||||
|
| [Authzed / SpiceDB](https://authzed.com/pricing) | Apache-2.0 authorization | 开源自托管免费;managed cloud 起步约 $2/小时;企业私有部署询价 | policy engine 开源,托管和企业运营收费 |
|
||||||
|
| [Element / Matrix](https://element.io/en/pricing) | Matrix 开放标准 | Community 免费;Enterprise 按 seat/月;Sovereign 按 deployment | 互操作不妨碍卖合规、审计、air-gap 与支持 |
|
||||||
|
| [Inrupt ESS](https://www.inrupt.com/products/enterprise-wallet-infrastructure) | Solid 标准 | 商业 license、企业 wallet infrastructure 与专业服务 | 用户数据层最直接的收入来自高合规部署 |
|
||||||
|
|
||||||
|
基于这些信号,机会优先级如下。
|
||||||
|
|
||||||
|
### 机会 A:迁移与 conformance 工具,最适合起步
|
||||||
|
|
||||||
|
做一个本地优先 CLI/SDK:读取 `.af`、Agent Spec、Agent Format、LangGraph thread/store dump 和常见 memory backend,输出 canonical capsule 与 loss report;再提供双向 round-trip、tamper、revocation、secret scan 和行为 smoke test。
|
||||||
|
|
||||||
|
开源核心可以是 Apache-2.0。收费点是私有 adapter、批量迁移、CI gate、迁移评估报告和技术支持。它不要求市场先接受一个全新协议,也能在协议竞争中保持中立。
|
||||||
|
|
||||||
|
### 机会 B:企业 Agent State Gateway,收入上限更高
|
||||||
|
|
||||||
|
在 harness 与 state store 之间做 policy gateway:统一 consent、scope、retention、forget、source provenance、poisoned-memory quarantine 和审计导出。对外同时暴露 MCP、Agent Protocol 和普通 REST;底层支持本地文件、Postgres、Solid/S3。
|
||||||
|
|
||||||
|
客户不是普通 C 端用户,而是已经部署多个 Agent runtime、又面临数据边界和合规审计的企业团队。收费可按活跃 Agent、写入量、存储、策略节点与 SLA。
|
||||||
|
|
||||||
|
### 机会 C:托管同步、备份与恢复,适合第二阶段
|
||||||
|
|
||||||
|
开放同步协议,卖默认 relay、加密备份、跨设备恢复、地域部署和企业密钥托管。必须允许用户随时导出并迁移,否则商业层会破坏协议最重要的价值主张。
|
||||||
|
|
||||||
|
### 机会 D:兼容 registry 与采购认证,网络效应最大但最慢
|
||||||
|
|
||||||
|
建立公开 test corpus 和 badge:某个 harness 是否支持 profile、memory、task、revocation、round-trip、loss declaration。长期可以成为采购与生态分发入口;短期很难单独收费,应由 A/B 的真实使用推动。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
_图 2|最值得下注的不是“拥有协议名字”,而是成为迁移、兼容与治理的默认实现。_
|
||||||
|
|
||||||
|
## 八、什么不值得做
|
||||||
|
|
||||||
|
### 8.1 不要做第五个只有 JSON Schema 的“开放标准”
|
||||||
|
|
||||||
|
Schema 很容易复制,采用才是护城河。没有两个独立 importer、round-trip test、版本迁移和失败语料,标准只是项目自己的配置文件。
|
||||||
|
|
||||||
|
### 8.2 不要先做通用 memory SDK
|
||||||
|
|
||||||
|
Mem0、Letta、Cognee、Zep/Graphiti 与各家 runtime 已经覆盖存储和检索。新项目若只做 `remember/recall`,会直接进入红海。兼容层应让客户保留现有 backend,而不是要求再次迁库。
|
||||||
|
|
||||||
|
### 8.3 不要把所有状态都做成 CRDT
|
||||||
|
|
||||||
|
偏好 map、标签和协作文档适合 CRDT;授权撤销、审计和一次性副作用更适合签名事件与明确的执行规则。把“自动合并”扩展到付款、发信、删除和工具执行,会制造重复副作用。
|
||||||
|
|
||||||
|
### 8.4 不要发币或把状态写入公链
|
||||||
|
|
||||||
|
技术上没有必要:这里没有双花问题,用户也不需要所有状态获得全球共识。商业上它会把产品从开发者基础设施拖进金融与投机叙事。对北京团队尤其不划算:2026 年 2 月八部门通知再次明确,境内虚拟货币兑换、代币发行融资和相关金融服务属于严格禁止的非法金融活动。[证监会转载八部门通知](https://www.csrc.gov.cn/csrc/c100028/c7614318/content.shtml)
|
||||||
|
|
||||||
|
可以使用 hash、签名、DID 和 capability;这些是密码学工具,不等于发行虚拟货币。
|
||||||
|
|
||||||
|
## 九、90 天的验证路线
|
||||||
|
|
||||||
|
### 第 1–2 周:只定义迁移契约
|
||||||
|
|
||||||
|
- 冻结五个 namespace:profile、memory、task、grant、artifact。
|
||||||
|
- 定义 `loss-manifest.json`、secret policy 和 extension namespace。
|
||||||
|
- 选择一个现有容器格式,不争论 YAML、JSON 还是 CBOR;优先 JSON + content-addressed blob。
|
||||||
|
|
||||||
|
### 第 3–5 周:做两条真实 adapter
|
||||||
|
|
||||||
|
- 第一条:Letta `.af` ↔ canonical capsule。
|
||||||
|
- 第二条:LangGraph thread/store 或 Agent Protocol ↔ canonical capsule。
|
||||||
|
- 每条都做 export → import → export round-trip,并对丢失字段分类。
|
||||||
|
|
||||||
|
### 第 6–8 周:把测试做成产品
|
||||||
|
|
||||||
|
- conformance CLI、GitHub Action、自包含 fixture。
|
||||||
|
- tamper、expiry、revocation、schema upgrade、secret leak、concurrent merge 测试。
|
||||||
|
- 生成可给安全/采购团队阅读的 HTML 报告。
|
||||||
|
|
||||||
|
### 第 9–12 周:找两个设计伙伴,而不是发大协议
|
||||||
|
|
||||||
|
优先对象是同时使用两种 runtime、已有真实 memory 数据、愿意提供脱敏迁移样本的 Agent 工具团队或企业平台团队。先卖一次迁移评估或私有 adapter,再判断是否值得托管。
|
||||||
|
|
||||||
|
三个停止条件:
|
||||||
|
|
||||||
|
1. 6 个月内没有两个独立 harness 愿意维护 adapter;
|
||||||
|
2. 三次真实迁移中,关键字段平均超过 20% 只能 opaque 保存且客户不接受;
|
||||||
|
3. 客户愿意用开源 CLI,却没有任何人愿意为批量迁移、治理或 SLA 付费。
|
||||||
|
|
||||||
|
若触发其中两项,就应把项目收缩为开源工具,不再押注协议公司。
|
||||||
|
|
||||||
|
## 十、最终建议
|
||||||
|
|
||||||
|
这不是“推翻互联网根基”的项目。更准确的类比是:**给 Agent 时代补一个尚未稳定的用户数据 portability seam。** 它可能形成协议级网络效应,但成功路径不会像比特币那样先创造资产和共识网络,而更像 PostgreSQL、Matrix、WireGuard 或 OpenAPI:开放核心先解决真实兼容问题,商业公司再卖默认托管、企业控制面和可信运维。
|
||||||
|
|
||||||
|
如果现在只有一个人或小团队,我会选择以下产品定义:
|
||||||
|
|
||||||
|
> **一个开源的 Agent state migration + conformance toolkit,先支持 `.af` 与 Agent Protocol/LangGraph,强制输出 loss report;再把企业授权、审计和托管同步做成付费 gateway。**
|
||||||
|
|
||||||
|
它的优势是每一步都能独立创造价值,也保留了成为公共协议层的上行空间。相反,从白皮书、基金会、代币或“大一统 Agent 标准”起步,会把最难的生态冷启动放在最前面。
|
||||||
|
|
||||||
|
## 资料与复现说明
|
||||||
|
|
||||||
|
本文优先采用官方规范、官方仓库固定提交、W3C 文档、arXiv 原文与实时官方定价页。GitHub star 只作采用信号,不作为质量评分;产品价格只证明收费方式存在,不外推收入或市场规模。Portable Agent Memory 与 memorywire 的论文结论均按预印本处理。私有调研记录、原始仓库镜像和中间证据不进入公开仓库;公开实验不含任何 credential 或真实用户数据。
|
||||||
|
|
||||||
|
### 视觉来源
|
||||||
|
|
||||||
|
题图由 OpenAI ImageGen 生成,最终 prompt 为:`A sophisticated editorial technology illustration for a Chinese research article about portable AI agent state across multiple independent agent runtimes. Wide 16:9 composition, deep navy-black background. Show several distinct translucent modular runtime containers around the edges, each with different abstract internal circuitry, while a luminous user-controlled state capsule travels between them through a neutral shared layer. Include subtle visual motifs of cryptographic signatures, scoped capability keys, content-addressed event blocks, and local-first synchronized shards. Clean systems-design aesthetic, restrained cyan, electric blue, violet and warm coral accents, precise depth, premium research publication quality. No text, no letters, no numbers, no logos, no people, no company branding.` 图 1 与图 2 为确定性 SVG,标签和位置来自本文证据矩阵与机会判断。
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="820" viewBox="0 0 1280 820" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">可移植 Agent 状态市场机会图</title>
|
||||||
|
<desc id="desc">横轴是对协议网络效应的依赖,纵轴是近期付费意愿。兼容迁移和企业治理位于高付费区,第五个新格式与代币网络位于高网络效应、低近期付费区。</desc>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg2" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f8fafc"/><stop offset="1" stop-color="#eef2ff"/></linearGradient>
|
||||||
|
<filter id="soft"><feDropShadow dx="0" dy="8" stdDeviation="9" flood-color="#475569" flood-opacity=".16"/></filter>
|
||||||
|
<marker id="arr2" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0 0 L9 4.5 L0 9Z" fill="#475569"/></marker>
|
||||||
|
</defs>
|
||||||
|
<rect width="1280" height="820" rx="32" fill="url(#bg2)"/>
|
||||||
|
<text x="70" y="70" fill="#0f172a" font-size="32" font-weight="800" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">先卖迁移与治理,再争协议网络效应</text>
|
||||||
|
<text x="70" y="104" fill="#64748b" font-size="18" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">位置是基于公开定价与本次技术审计的判断,不是市场规模预测</text>
|
||||||
|
<g transform="translate(132 148)" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">
|
||||||
|
<rect x="0" y="0" width="1010" height="560" rx="22" fill="#ffffff" stroke="#cbd5e1"/>
|
||||||
|
<rect x="0" y="0" width="505" height="280" fill="#ecfeff" opacity=".72"/>
|
||||||
|
<rect x="505" y="0" width="505" height="280" fill="#f5f3ff" opacity=".75"/>
|
||||||
|
<rect x="0" y="280" width="505" height="280" fill="#f8fafc"/>
|
||||||
|
<rect x="505" y="280" width="505" height="280" fill="#fff7ed" opacity=".72"/>
|
||||||
|
<line x1="505" y1="0" x2="505" y2="560" stroke="#cbd5e1" stroke-dasharray="8 8"/>
|
||||||
|
<line x1="0" y1="280" x2="1010" y2="280" stroke="#cbd5e1" stroke-dasharray="8 8"/>
|
||||||
|
<line x1="0" y1="560" x2="1038" y2="560" stroke="#475569" stroke-width="2" marker-end="url(#arr2)"/>
|
||||||
|
<line x1="0" y1="560" x2="0" y2="-26" stroke="#475569" stroke-width="2" marker-end="url(#arr2)"/>
|
||||||
|
<text x="405" y="612" fill="#334155" font-size="18" font-weight="700">对协议网络效应的依赖 →</text>
|
||||||
|
<text transform="translate(-72 385) rotate(-90)" fill="#334155" font-size="18" font-weight="700">近期付费意愿 →</text>
|
||||||
|
<text x="22" y="34" fill="#0891b2" font-size="15" font-weight="700">先做:价值可独立交付</text>
|
||||||
|
<text x="527" y="34" fill="#7c3aed" font-size="15" font-weight="700">再做:需要生态协同</text>
|
||||||
|
|
||||||
|
<g filter="url(#soft)">
|
||||||
|
<rect x="72" y="84" width="300" height="112" rx="20" fill="#ffffff" stroke="#06b6d4" stroke-width="2"/>
|
||||||
|
<circle cx="108" cy="120" r="16" fill="#06b6d4"/>
|
||||||
|
<text x="138" y="124" fill="#0f172a" font-size="22" font-weight="800">兼容迁移 + loss report</text>
|
||||||
|
<text x="96" y="158" fill="#475569" font-size="16">CLI、adapter、迁移评估、回滚</text>
|
||||||
|
|
||||||
|
<rect x="610" y="66" width="318" height="124" rx="20" fill="#ffffff" stroke="#8b5cf6" stroke-width="2"/>
|
||||||
|
<circle cx="648" cy="105" r="16" fill="#8b5cf6"/>
|
||||||
|
<text x="678" y="110" fill="#0f172a" font-size="22" font-weight="800">企业治理 gateway</text>
|
||||||
|
<text x="634" y="145" fill="#475569" font-size="16">授权、撤销、审计、合规策略</text>
|
||||||
|
|
||||||
|
<rect x="278" y="218" width="280" height="108" rx="20" fill="#ffffff" stroke="#14b8a6" stroke-width="2"/>
|
||||||
|
<circle cx="314" cy="254" r="15" fill="#14b8a6"/>
|
||||||
|
<text x="344" y="259" fill="#0f172a" font-size="21" font-weight="800">托管同步 / 恢复</text>
|
||||||
|
<text x="302" y="292" fill="#475569" font-size="16">按写入、存储、SLA 收费</text>
|
||||||
|
|
||||||
|
<rect x="642" y="328" width="272" height="106" rx="20" fill="#ffffff" stroke="#f59e0b" stroke-width="2"/>
|
||||||
|
<circle cx="678" cy="364" r="15" fill="#f59e0b"/>
|
||||||
|
<text x="708" y="369" fill="#0f172a" font-size="21" font-weight="800">第五个新格式</text>
|
||||||
|
<text x="666" y="402" fill="#475569" font-size="16">先有生态,后有收入</text>
|
||||||
|
|
||||||
|
<rect x="768" y="450" width="204" height="78" rx="20" fill="#ffffff" stroke="#fb7185" stroke-width="2"/>
|
||||||
|
<text x="798" y="483" fill="#be123c" font-size="20" font-weight="800">代币 / 状态链</text>
|
||||||
|
<text x="798" y="510" fill="#64748b" font-size="14">高监管与冷启动风险</text>
|
||||||
|
|
||||||
|
<rect x="90" y="380" width="264" height="92" rx="20" fill="#ffffff" stroke="#94a3b8"/>
|
||||||
|
<text x="122" y="416" fill="#334155" font-size="20" font-weight="800">单一 memory SDK</text>
|
||||||
|
<text x="122" y="446" fill="#64748b" font-size="15">红海;已有 Mem0/Letta 等</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<text x="70" y="775" fill="#64748b" font-size="15" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">图 2|最值得下注的不是“拥有协议名字”,而是成为迁移、兼容与治理的默认实现。</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"experiment": "portable-state-probe",
|
||||||
|
"date": "2026-08-22",
|
||||||
|
"runtime": "Node.js v26.5.0",
|
||||||
|
"tests": {
|
||||||
|
"passed": 10,
|
||||||
|
"failed": 0
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"canonical_state_fields": 4,
|
||||||
|
"converged_state_hash": "d96f9b67696866926cb8a495cac7f23d3d468d2cc9af7de2fec8b07534e35688",
|
||||||
|
"tamper_rejections": 1,
|
||||||
|
"revocation_rejections": 1,
|
||||||
|
"explicit_loss_items": 1,
|
||||||
|
"omitted_secret_classes": 1
|
||||||
|
},
|
||||||
|
"agent_file_audit": {
|
||||||
|
"agents": 1,
|
||||||
|
"blocks": 9,
|
||||||
|
"tools": 9,
|
||||||
|
"messages": 3,
|
||||||
|
"contains_secret_values": false
|
||||||
|
},
|
||||||
|
"demonstrated": [
|
||||||
|
"cross-harness preference, memory and task import",
|
||||||
|
"signed event verification",
|
||||||
|
"delivery-order-independent convergence",
|
||||||
|
"post-revocation write rejection",
|
||||||
|
"secret omission",
|
||||||
|
"explicit semantic loss reporting"
|
||||||
|
],
|
||||||
|
"not_demonstrated": [
|
||||||
|
"behavioral equivalence across real model runtimes",
|
||||||
|
"large-scale sync performance",
|
||||||
|
"key recovery",
|
||||||
|
"multi-user policy composition"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
function canonical(value) {
|
||||||
|
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
||||||
|
}
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function digest(value) {
|
||||||
|
return crypto.createHash("sha256").update(canonical(value)).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
function device(name) {
|
||||||
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
||||||
|
const publicPem = publicKey.export({ type: "spki", format: "pem" });
|
||||||
|
return { name, did: `did:key:${digest(publicPem).slice(0, 32)}`, publicKey, privateKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEvent(signer, body) {
|
||||||
|
const id = digest(body);
|
||||||
|
const signature = crypto.sign(null, Buffer.from(canonical(body)), signer.privateKey).toString("base64");
|
||||||
|
return { id, body, signature };
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyEvent(event, keys) {
|
||||||
|
if (digest(event.body) !== event.id) return false;
|
||||||
|
const key = keys.get(event.body.writer);
|
||||||
|
return Boolean(key && crypto.verify(null, Buffer.from(canonical(event.body)), key, Buffer.from(event.signature, "base64")));
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventOrder(a, b) {
|
||||||
|
return a.body.clock - b.body.clock || a.id.localeCompare(b.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reduce(events, keys, ownerDid) {
|
||||||
|
const unique = [...new Map(events.map((event) => [event.id, event])).values()].sort(eventOrder);
|
||||||
|
const rejected = [];
|
||||||
|
const grants = new Map();
|
||||||
|
const revocations = new Map();
|
||||||
|
|
||||||
|
for (const event of unique) {
|
||||||
|
if (!verifyEvent(event, keys)) {
|
||||||
|
rejected.push({ id: event.id, reason: "bad-signature-or-id" });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const { kind, writer, payload, clock } = event.body;
|
||||||
|
if (kind === "grant.issue") {
|
||||||
|
if (writer !== ownerDid) rejected.push({ id: event.id, reason: "non-owner-grant" });
|
||||||
|
else grants.set(payload.grant_id, payload);
|
||||||
|
}
|
||||||
|
if (kind === "grant.revoke") {
|
||||||
|
if (writer !== ownerDid) rejected.push({ id: event.id, reason: "non-owner-revocation" });
|
||||||
|
else revocations.set(payload.grant_id, clock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const registers = new Map();
|
||||||
|
for (const event of unique) {
|
||||||
|
if (!verifyEvent(event, keys) || event.body.kind !== "state.put") continue;
|
||||||
|
const { writer, clock, payload, grant_id: grantId } = event.body;
|
||||||
|
const grant = grants.get(grantId);
|
||||||
|
const revokedAt = revocations.get(grantId);
|
||||||
|
let reason = null;
|
||||||
|
if (!grant) reason = "missing-grant";
|
||||||
|
else if (grant.grantee !== writer) reason = "wrong-grantee";
|
||||||
|
else if (!grant.actions.includes("state.put")) reason = "action-not-allowed";
|
||||||
|
else if (clock > grant.expires_at) reason = "expired-grant";
|
||||||
|
else if (revokedAt !== undefined && clock >= revokedAt) reason = "revoked-grant";
|
||||||
|
if (reason) {
|
||||||
|
rejected.push({ id: event.id, reason });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const current = registers.get(payload.path);
|
||||||
|
if (!current || eventOrder(current.event, event) < 0) registers.set(payload.path, { value: payload.value, event });
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = Object.fromEntries([...registers.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([path, item]) => [path, item.value]));
|
||||||
|
return { state, state_hash: digest(state), accepted_events: unique.length - rejected.length, rejected };
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportHarnessA(internal, signer, ownerDid, grantId, startClock) {
|
||||||
|
const events = [];
|
||||||
|
let clock = startClock;
|
||||||
|
const put = (path, value) => events.push(createEvent(signer, {
|
||||||
|
version: "probe-0.1", owner: ownerDid, writer: signer.did, clock: clock++, kind: "state.put", grant_id: grantId, payload: { path, value }
|
||||||
|
}));
|
||||||
|
for (const [key, value] of Object.entries(internal.user.preferences)) put(`profile.preferences.${key}`, value);
|
||||||
|
for (const memory of internal.memories) put(`memory.${memory.id}`, { text: memory.text, provenance: memory.source });
|
||||||
|
put("task.current", internal.task);
|
||||||
|
return {
|
||||||
|
events,
|
||||||
|
loss: ["tool_rules: target-independent execution semantics undefined"],
|
||||||
|
omitted: ["private_material: never serialized"]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function importHarnessB(state) {
|
||||||
|
const profile = { preferences: {} };
|
||||||
|
const recollections = {};
|
||||||
|
let work = null;
|
||||||
|
for (const [path, value] of Object.entries(state)) {
|
||||||
|
if (path.startsWith("profile.preferences.")) profile.preferences[path.split(".").at(-1)] = value;
|
||||||
|
else if (path.startsWith("memory.")) recollections[path.split(".").at(-1)] = value;
|
||||||
|
else if (path === "task.current") work = value;
|
||||||
|
}
|
||||||
|
return { profile, recollections, work, vault: "local-only" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function auditAgentFile(path) {
|
||||||
|
if (!path) return null;
|
||||||
|
const file = JSON.parse(fs.readFileSync(path, "utf8"));
|
||||||
|
const agents = file.agents ?? [];
|
||||||
|
return {
|
||||||
|
format: "Letta Agent File",
|
||||||
|
agents: agents.length,
|
||||||
|
blocks: (file.blocks ?? []).length,
|
||||||
|
tools: (file.tools ?? []).length,
|
||||||
|
messages: agents.reduce((sum, agent) => sum + (agent.messages ?? []).length, 0),
|
||||||
|
contains_secret_values: agents.some((agent) => Object.values(agent.secrets ?? {}).some((value) => value !== null)),
|
||||||
|
known_portability_gaps: ["archival passages not represented", "framework-specific blocks/tool rules require adapters", "schema migration is roadmap work"]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const owner = device("owner");
|
||||||
|
const harnessA = device("harness-a");
|
||||||
|
const harnessB = device("harness-b");
|
||||||
|
const keys = new Map([[owner.did, owner.publicKey], [harnessA.did, harnessA.publicKey], [harnessB.did, harnessB.publicKey]]);
|
||||||
|
const grantA = createEvent(owner, { version: "probe-0.1", owner: owner.did, writer: owner.did, clock: 1, kind: "grant.issue", payload: { grant_id: "grant-a", grantee: harnessA.did, actions: ["state.put"], expires_at: 100 } });
|
||||||
|
const grantB = createEvent(owner, { version: "probe-0.1", owner: owner.did, writer: owner.did, clock: 2, kind: "grant.issue", payload: { grant_id: "grant-b", grantee: harnessB.did, actions: ["state.put"], expires_at: 100 } });
|
||||||
|
|
||||||
|
const internalA = {
|
||||||
|
user: { preferences: { language: "zh-CN", answer_style: "direct" } },
|
||||||
|
memories: [{ id: "m1", text: "User works in Beijing", source: "user-stated" }],
|
||||||
|
task: { id: "research-1", status: "active", title: "portable state research" },
|
||||||
|
tool_rules: [{ tool: "shell", allow: "workspace-only" }],
|
||||||
|
private_material: { demo_credential: "redacted-local-only" }
|
||||||
|
};
|
||||||
|
const exportedA = exportHarnessA(internalA, harnessA, owner.did, "grant-a", 10);
|
||||||
|
const base = reduce([grantA, grantB, ...exportedA.events], keys, owner.did);
|
||||||
|
const importedB = importHarnessB(base.state);
|
||||||
|
|
||||||
|
const concurrentA = createEvent(harnessA, { version: "probe-0.1", owner: owner.did, writer: harnessA.did, clock: 20, kind: "state.put", grant_id: "grant-a", payload: { path: "profile.preferences.theme", value: "dark" } });
|
||||||
|
const concurrentB = createEvent(harnessB, { version: "probe-0.1", owner: owner.did, writer: harnessB.did, clock: 20, kind: "state.put", grant_id: "grant-b", payload: { path: "profile.preferences.theme", value: "light" } });
|
||||||
|
const mergedAB = reduce([grantA, grantB, ...exportedA.events, concurrentA, concurrentB], keys, owner.did);
|
||||||
|
const mergedBA = reduce([concurrentB, ...exportedA.events, grantB, concurrentA, grantA], keys, owner.did);
|
||||||
|
|
||||||
|
const tampered = structuredClone(exportedA.events[0]);
|
||||||
|
tampered.body.payload.value = "tampered";
|
||||||
|
const tamperResult = reduce([grantA, tampered], keys, owner.did);
|
||||||
|
|
||||||
|
const revokeB = createEvent(owner, { version: "probe-0.1", owner: owner.did, writer: owner.did, clock: 30, kind: "grant.revoke", payload: { grant_id: "grant-b" } });
|
||||||
|
const postRevoke = createEvent(harnessB, { version: "probe-0.1", owner: owner.did, writer: harnessB.did, clock: 31, kind: "state.put", grant_id: "grant-b", payload: { path: "profile.preferences.language", value: "en-US" } });
|
||||||
|
const revokeResult = reduce([grantA, grantB, ...exportedA.events, revokeB, postRevoke], keys, owner.did);
|
||||||
|
|
||||||
|
const tests = [];
|
||||||
|
const test = (name, fn) => { fn(); tests.push({ name, status: "pass" }); };
|
||||||
|
test("cross-harness preference import", () => assert.equal(importedB.profile.preferences.language, "zh-CN"));
|
||||||
|
test("cross-harness memory import", () => assert.equal(importedB.recollections.m1.text, "User works in Beijing"));
|
||||||
|
test("cross-harness task import", () => assert.equal(importedB.work.status, "active"));
|
||||||
|
test("merge converges independent of delivery order", () => assert.equal(mergedAB.state_hash, mergedBA.state_hash));
|
||||||
|
test("concurrent conflict resolves deterministically", () => assert.equal(mergedAB.state["profile.preferences.theme"], mergedBA.state["profile.preferences.theme"]));
|
||||||
|
test("tampering is rejected", () => assert.equal(tamperResult.rejected[0].reason, "bad-signature-or-id"));
|
||||||
|
test("revoked grant blocks later write", () => assert.ok(revokeResult.rejected.some((item) => item.reason === "revoked-grant")));
|
||||||
|
test("pre-revocation state remains valid", () => assert.equal(revokeResult.state["profile.preferences.language"], "zh-CN"));
|
||||||
|
test("secrets are omitted", () => assert.ok(!canonical(exportedA).includes("redacted-local-only")));
|
||||||
|
test("semantic loss is explicit", () => assert.ok(exportedA.loss[0].includes("tool_rules")));
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
experiment: "portable-state-probe",
|
||||||
|
runtime: process.version,
|
||||||
|
tests,
|
||||||
|
summary: {
|
||||||
|
passed: tests.length,
|
||||||
|
failed: 0,
|
||||||
|
canonical_state_fields: Object.keys(base.state).length,
|
||||||
|
converged_state_hash: mergedAB.state_hash,
|
||||||
|
tamper_rejections: tamperResult.rejected.length,
|
||||||
|
revocation_rejections: revokeResult.rejected.filter((item) => item.reason === "revoked-grant").length,
|
||||||
|
explicit_loss_items: exportedA.loss.length,
|
||||||
|
omitted_secret_classes: exportedA.omitted.length
|
||||||
|
},
|
||||||
|
interpretation: {
|
||||||
|
demonstrated: ["signed event portability", "deterministic merge", "capability revocation", "secret omission", "loss reporting"],
|
||||||
|
not_demonstrated: ["behavioral equivalence across real model runtimes", "large-scale sync performance", "key recovery", "multi-user policy composition"]
|
||||||
|
},
|
||||||
|
agent_file_audit: auditAgentFile(process.argv[2])
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="900" viewBox="0 0 1440 900" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">可移植 Agent 状态层与现有协议的关系</title>
|
||||||
|
<desc id="desc">现有 MCP、A2A 和 AG-UI 位于交互接口侧;Agent File、Agent Spec 等位于适配层;真正缺少的是包含语义、损失声明、授权、签名事件和同步的用户状态公共层。</desc>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0" stop-color="#071426"/>
|
||||||
|
<stop offset="1" stop-color="#11102b"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="core" x1="0" y1="0" x2="1" y2="0">
|
||||||
|
<stop offset="0" stop-color="#0ea5e9"/>
|
||||||
|
<stop offset="0.55" stop-color="#7c3aed"/>
|
||||||
|
<stop offset="1" stop-color="#f97372"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="12" stdDeviation="14" flood-color="#020617" flood-opacity=".42"/>
|
||||||
|
</filter>
|
||||||
|
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
||||||
|
<path d="M0,0 L10,5 L0,10 Z" fill="#64748b"/>
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
<rect width="1440" height="900" rx="36" fill="url(#bg)"/>
|
||||||
|
<text x="80" y="76" fill="#f8fafc" font-size="34" font-weight="700" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">跨 harness 公共层不是另一个 harness</text>
|
||||||
|
<text x="80" y="112" fill="#94a3b8" font-size="18" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">状态归用户;运行时只通过 adapter 读取、写入和声明损失</text>
|
||||||
|
|
||||||
|
<g filter="url(#shadow)" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">
|
||||||
|
<rect x="96" y="158" width="930" height="118" rx="24" fill="#111d33" stroke="#334155"/>
|
||||||
|
<text x="126" y="193" fill="#7dd3fc" font-size="16" font-weight="700">HARNESSES / 运行时</text>
|
||||||
|
<g fill="#e2e8f0" font-size="22" font-weight="600">
|
||||||
|
<rect x="126" y="214" width="190" height="42" rx="12" fill="#162941"/><text x="158" y="242">DeepSeek Harness</text>
|
||||||
|
<rect x="334" y="214" width="160" height="42" rx="12" fill="#162941"/><text x="366" y="242">LangGraph</text>
|
||||||
|
<rect x="512" y="214" width="140" height="42" rx="12" fill="#162941"/><text x="554" y="242">Letta</text>
|
||||||
|
<rect x="670" y="214" width="180" height="42" rx="12" fill="#162941"/><text x="706" y="242">自研 runtime</text>
|
||||||
|
<rect x="868" y="214" width="128" height="42" rx="12" fill="#162941"/><text x="900" y="242">未来实现</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<rect x="96" y="314" width="930" height="118" rx="24" fill="#121e35" stroke="#38bdf8" stroke-opacity=".52"/>
|
||||||
|
<text x="126" y="349" fill="#7dd3fc" font-size="16" font-weight="700">ADAPTER / 兼容与能力协商</text>
|
||||||
|
<g fill="#e2e8f0" font-size="19" font-weight="600">
|
||||||
|
<rect x="126" y="370" width="160" height="42" rx="12" fill="#17304b"/><text x="158" y="398">Agent File</text>
|
||||||
|
<rect x="304" y="370" width="176" height="42" rx="12" fill="#17304b"/><text x="336" y="398">Agent Spec</text>
|
||||||
|
<rect x="498" y="370" width="184" height="42" rx="12" fill="#17304b"/><text x="530" y="398">Agent Format</text>
|
||||||
|
<rect x="700" y="370" width="166" height="42" rx="12" fill="#17304b"/><text x="732" y="398">memorywire</text>
|
||||||
|
<rect x="884" y="370" width="112" height="42" rx="12" fill="#17304b"/><text x="912" y="398">转换器</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<rect x="96" y="470" width="930" height="170" rx="28" fill="#11182e" stroke="url(#core)" stroke-width="3"/>
|
||||||
|
<rect x="96" y="470" width="12" height="170" rx="6" fill="url(#core)"/>
|
||||||
|
<text x="130" y="508" fill="#f8fafc" font-size="18" font-weight="800">用户拥有的可移植状态语义</text>
|
||||||
|
<g fill="#e2e8f0" font-size="20" font-weight="600">
|
||||||
|
<rect x="130" y="532" width="150" height="42" rx="13" fill="#1e2a4a"/><text x="162" y="560">偏好 / 身份</text>
|
||||||
|
<rect x="296" y="532" width="146" height="42" rx="13" fill="#1e2a4a"/><text x="328" y="560">长期 memory</text>
|
||||||
|
<rect x="458" y="532" width="146" height="42" rx="13" fill="#1e2a4a"/><text x="490" y="560">任务 checkpoint</text>
|
||||||
|
<rect x="620" y="532" width="160" height="42" rx="13" fill="#1e2a4a"/><text x="652" y="560">授权 / 撤销</text>
|
||||||
|
<rect x="796" y="532" width="198" height="42" rx="13" fill="#1e2a4a"/><text x="828" y="560">来源 / 审计</text>
|
||||||
|
<rect x="130" y="586" width="240" height="34" rx="11" fill="#3a2043"/><text x="158" y="610" fill="#fda4af" font-size="17">必须输出 loss manifest</text>
|
||||||
|
<text x="392" y="610" fill="#94a3b8" font-size="17" font-weight="400">语法可迁移,不承诺不同模型与工具执行完全等价</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<rect x="96" y="678" width="930" height="126" rx="24" fill="#101b2f" stroke="#334155"/>
|
||||||
|
<text x="126" y="713" fill="#a78bfa" font-size="16" font-weight="700">CONTROL + SYNC / 可复用的成熟部件</text>
|
||||||
|
<g fill="#e2e8f0" font-size="18" font-weight="600">
|
||||||
|
<rect x="126" y="734" width="150" height="46" rx="12" fill="#261f45"/><text x="158" y="763">DID / device key</text>
|
||||||
|
<rect x="294" y="734" width="160" height="46" rx="12" fill="#261f45"/><text x="326" y="763">UCAN capability</text>
|
||||||
|
<rect x="472" y="734" width="164" height="46" rx="12" fill="#261f45"/><text x="504" y="763">签名事件日志</text>
|
||||||
|
<rect x="654" y="734" width="148" height="46" rx="12" fill="#261f45"/><text x="686" y="763">Automerge</text>
|
||||||
|
<rect x="820" y="734" width="176" height="46" rx="12" fill="#261f45"/><text x="852" y="763">Solid / 对象存储</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">
|
||||||
|
<rect x="1080" y="158" width="264" height="646" rx="26" fill="#0e1a2d" stroke="#334155"/>
|
||||||
|
<text x="1112" y="198" fill="#f8fafc" font-size="20" font-weight="700">相邻协议</text>
|
||||||
|
<text x="1112" y="226" fill="#94a3b8" font-size="15">需要组合,但不会替代状态层</text>
|
||||||
|
<rect x="1112" y="266" width="200" height="104" rx="18" fill="#162941" stroke="#38bdf8" stroke-opacity=".45"/>
|
||||||
|
<text x="1138" y="304" fill="#7dd3fc" font-size="24" font-weight="800">MCP</text>
|
||||||
|
<text x="1138" y="334" fill="#cbd5e1" font-size="16">工具、资源、OAuth</text>
|
||||||
|
<rect x="1112" y="394" width="200" height="104" rx="18" fill="#211d3c" stroke="#8b5cf6" stroke-opacity=".52"/>
|
||||||
|
<text x="1138" y="432" fill="#c4b5fd" font-size="24" font-weight="800">A2A</text>
|
||||||
|
<text x="1138" y="462" fill="#cbd5e1" font-size="16">Agent 消息、任务、产物</text>
|
||||||
|
<rect x="1112" y="522" width="200" height="104" rx="18" fill="#302033" stroke="#fb7185" stroke-opacity=".5"/>
|
||||||
|
<text x="1138" y="560" fill="#fda4af" font-size="24" font-weight="800">AG-UI</text>
|
||||||
|
<text x="1138" y="590" fill="#cbd5e1" font-size="16">前端事件与共享视图</text>
|
||||||
|
<rect x="1112" y="650" width="200" height="104" rx="18" fill="#1a2837" stroke="#64748b"/>
|
||||||
|
<text x="1138" y="688" fill="#cbd5e1" font-size="22" font-weight="800">模型 API</text>
|
||||||
|
<text x="1138" y="718" fill="#94a3b8" font-size="16">推理兼容,不搬状态</text>
|
||||||
|
</g>
|
||||||
|
<g stroke="#64748b" stroke-width="2" fill="none" marker-end="url(#arrow)" opacity=".8">
|
||||||
|
<path d="M1028 328 C1060 328 1060 318 1098 318"/>
|
||||||
|
<path d="M1028 372 C1060 372 1060 446 1098 446"/>
|
||||||
|
<path d="M1028 526 C1060 526 1060 574 1098 574"/>
|
||||||
|
</g>
|
||||||
|
<text x="80" y="855" fill="#64748b" font-size="15" font-family="system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif">图 1|建议架构:已有协议负责调用和通信;新工作的核心是可移植语义、损失声明与一致性测试。</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.7 KiB |
Reference in New Issue
Block a user