路由后端 — 按路径前缀转发到不同子 backend。
让一个会话同时挂载多种存储,例如:
/workspace/→Sandbox.Docker(隔离代码执行)/memories/→Backend.Postgres(持久化记忆,跨 session)/conversation_history/→Backend.State(短期 ETS)- 默认(其他路径)→
Backend.Filesystem(本地工作目录)
这是 ACP(Zed / Cursor)部署模式的事实标准。
路由规则
- 按
routes中 prefix 从最长到最短匹配 - 命中 prefix → strip 该前缀后转发到子 backend(virtual_mode 友好)
- 未命中 → fallback 到
:defaultbackend :default必填
示例
backend =
CMDC.Backend.Composite.new(
default: CMDC.Backend.Filesystem.new(root_dir: "/tmp/ws"),
routes: %{
"/memories/" => CMDC.Backend.State.new(:agent_memories),
"/conversation_history/" => CMDC.Backend.State.new(:histories)
}
)
# 走 default (Filesystem) — 实际路径 /tmp/ws/notes/todo.md
CMDC.Backend.write(backend, "/notes/todo.md", "buy milk")
# 走 State 表 :agent_memories — 子 backend 看到的路径就是 /user-id.md
CMDC.Backend.write(backend, "/memories/user-id.md", "John")
Summary
Types
@type t() :: %CMDC.Backend.Composite{ default: CMDC.Backend.t(), routes: %{required(String.t()) => CMDC.Backend.t()}, sorted_prefixes: [String.t()] }
Functions
构建 Composite backend。
选项
:default— 必填,默认 backend(未命中 routes 走它):routes—%{prefix => backend}map,prefix 必须以/开头 + 结尾
@spec route(t(), String.t()) :: {CMDC.Backend.t(), String.t()}
根据 path 找到对应 backend 与 strip 后的子路径。
匹配规则:按 sorted_prefixes(最长在前)逐个尝试,命中 prefix 则
返回 {routes[prefix], "/" <> trailing}。
Examples
iex> c = CMDC.Backend.Composite.new(
...> default: CMDC.Backend.State.new(:default_t),
...> routes: %{"/mem/" => CMDC.Backend.State.new(:mem_t)}
...> )
iex> {backend, sub} = CMDC.Backend.Composite.route(c, "/mem/key.txt")
iex> {backend.table, sub}
{:mem_t, "/key.txt"}