Skip to content

OCAS:自描述的类型化内容寻址存储

作者:星月 | 2026-06-16

一句话介绍

OCAS(Object Content Addressable Store) 是一个自描述的内容寻址存储——每个节点都带一个用 JSON Schema 定义的类型,数据一旦写入就不可变,用内容哈希寻址。它是 AI Agent 和确定性程序之间的类型化数据层:schema 即合约,让 Agent 的结构化产出可以被下游程序直接消费,无需解析。

为什么需要 OCAS?

普通的 KV 存储或文件系统有两个问题:

  1. 数据没有类型 —— 存进去的是一坨 JSON 或文本,读出来要靠调用方自己猜结构、自己解析。Agent 产出的数据尤其如此:一段 markdown、一个 JSON,下游程序拿到后还要写一堆 parse 逻辑,格式一变就崩。

  2. 数据会变 —— 同一个 key 的内容可以被覆盖,没法回答「这个结果是基于哪个版本的输入算出来的」。对需要审计、追溯、复现的 AI 工作流来说,这是致命的。

OCAS 的解法是把两件事钉死:

  • 类型钉死在数据里 —— 每个节点的 type 字段就是描述它的 JSON Schema 的哈希。数据自带「我是什么形状」的说明,下游程序按 schema 校验即可,不用猜。
  • 内容钉死在哈希里 —— 节点 ID 是它内容的 XXH64 哈希。内容相同则哈希相同,内容一变哈希就变。数据天然不可变、可追溯、可去重。

这就是 ocas 的定位:AI 和确定性程序之间的类型化数据层。在 United Workforce 里,moderator 直接消费 agent 的 typed 产出而无需 parse——因为产出已经是带 schema 的 CAS 节点了。

核心概念

节点:类型化的内容寻址单元

每个 CAS 节点有三部分:

  • payload —— 实际数据(一个 JSON 对象)
  • type —— 一个哈希,指向描述这个 payload 形状的 JSON Schema
  • hash —— 节点自己的 ID,由 payload + type 经确定性 CBOR 编码后取 XXH64 得到,13 字符 Crockford Base32

关键的自指设计:schema 本身也是节点。一个 schema 节点的 type 指向「元 schema(meta-schema)」——一个描述「合法的 JSON Schema 长什么样」的 schema。这条自描述链最终收敛到一个自引用的 bootstrap 节点。

引用:节点构成 DAG

payload 里的字段可以用 format: "cas_ref" 标记成对另一个节点的引用。这样节点之间就连成一张有向无环图(DAG),可以遍历、可以做闭包计算、可以整体导出。

bash
ocas refs <hash>                # 列出直接引用边
ocas walk <hash>                # 递归遍历整个 DAG
ocas walk <hash> --format tree  # 树状视图

变量:指向不可变数据的可变指针

节点不可变,但现实需要「可变的名字」——就像 git 的 branch 指向 commit。OCAS 用**变量(variable)**解决:变量是一个名字到哈希的可变绑定。

bash
ocas var set @myapp/config <hash>     # 把名字绑到哈希
ocas var get @myapp/config            # 查当前绑定
ocas var history @myapp/config        # 看历史值(LRU)

变量名必须遵循 @scope/name 格式(@ocas/* 为内置保留)。@ 前缀让名字在视觉上和哈希区分开。任何接受哈希的命令也接受变量名——CLI 内部用统一的 resolveHash 解析。

Envelope:自描述的输出格式

每个命令输出一个 { type, value } 信封——type 是结果 schema 的哈希,value 是 payload。输出本身也是自描述、可组合的:

bash
# 一个命令的输出可以直接 pipe 进另一个命令
ocas put @ocas/schema schema.json | ocas render -p

# 用 jq 提取
ocas list --type @ocas/schema | jq -r '.value[].hash'

render 是唯一输出原始文本而非信封的命令。

快速上手

bash
# 安装(首次使用自动创建并 bootstrap,无需 init)
pnpm add -g @ocas/cli
# 要求 Node.js >= 22.5.0(用了内置的 node:sqlite)

# 注册一个 schema(schema 也只是被元 schema 约束的节点)
echo '{
  "type": "object",
  "properties": { "title": { "type": "string" }, "done": { "type": "boolean" } },
  "required": ["title", "done"],
  "additionalProperties": false
}' | ocas put @ocas/schema -p
# → { "type": "...", "value": "1ABC2DEF34567" }

# 给它起个名字
ocas var set @todo/schema 1ABC2DEF34567

# 存一条 todo
echo '{ "title": "Buy milk", "done": false }' | ocas put @todo/schema -p

# 取出来
ocas get 9XYZ8WVU76543

# 校验完整性 + schema 合规
ocas verify 9XYZ8WVU76543

渲染系统:把数据变成文档

OCAS 不只是存数据,还能把 CAS 节点渲染成文本或 HTML 文档。这套渲染基于 LiquidJS 模板 + 三命名空间 + Map-Reduce-Compose 管线

三个并列的模板命名空间:

命名空间作用
Instance(@ocas/template/{format}/{type}每种类型一个模板,渲染出内容片段
Static(@ocas/template-static/{format}/{type}类型级别的 CSS/JS 资源
Compose(@ocas/template-compose/{format}文档外壳,包裹所有内容 + 去重后的静态资源

渲染时走三步:Map(DFS 渲染每个节点)→ Reduce(按类型去重收集静态资源,10 个 person 节点只产生 1 份 CSS)→ Compose(套进文档外壳)。

实例模板里可以直接用 payload 字段(自动展开),还有自定义的 render 标签做递归引用展开,带「分辨率衰减(resolution decay)」——越深的引用渲染得越粗略,实现渐进式加载。

bash
ocas template set <type> <file> [--format html]   # 设置实例模板
ocas render <hash> [--format html]                # 用模板渲染

架构

Monorepo,三个包:

packages/
├── core/   # @ocas/core — 核心引擎:哈希、schema、store、verify、bootstrap、render
├── fs/     # @ocas/fs — 文件系统后端(FS 存 CAS + SQLite 存 var/tag)
└── cli/    # @ocas/cli — ocas 命令行工具

几个关键设计:

  • @ocas/core 零 SQLite 依赖 —— 核心层不绑定任何存储后端,openStore() 返回统一的 Store(含 cas / var / tag 三个子存储)并自动 bootstrap。
  • 没有「别名」概念 —— 所有名字解析都走 store.var。内置 schema(@ocas/schema@ocas/string 等)在 bootstrap 时作为变量注册,和用户自定义变量一视同仁。
  • 统一存储组合 —— FS 后端用文件系统存不可变的 CAS 内容,用 SQLite 存可变的变量和标签,各取所长。

设计哲学

  1. 自描述(Self-describing) —— 数据携带自己的类型说明,不依赖外部约定。schema 也是节点,类型系统自我闭环。

  2. 不可变 + 内容寻址 —— 相同内容必然同哈希,天然去重、天然可追溯。可以精确回答「这个结果是基于哪些输入算出来的」。

  3. Schema 即合约 —— 这是 ocas 在 AI 工作流里的核心价值。Agent 的产出不是「一段需要解析的文本」,而是「一个带类型合约的节点」。确定性程序可以直接按 schema 消费,类型不匹配在写入时就被拒绝。

  4. CLI 即 API —— 每个命令输出自描述信封,命令之间可以自由组合。整个系统用普通命令行工具就能调试和检查,没有黑盒。

相关链接

Shazhou Studio