CMDC.Backend behaviour — 文件 / 状态 / 远程存储的统一访问层。
对标 deepagents BackendProtocol 1:1 对齐。让工具层(read_file / write_file /
edit_file / ls / glob / grep / execute 等)与底层执行环境完全解耦,支持任意
backend 实现(ETS / 本地文件 / Docker / Modal / E2B / Postgres ...)。
设计
- 10 个核心 callback:
- 文件操作:
ls/1read/3write/2edit/4grep/3glob/2 - 二进制传输:
upload_files/1download_files/1 - 沙盒扩展(可选):
execute/2id/0(由CMDC.Sandbox子 behaviour 引入)
- 文件操作:
- 统一 Result struct:见
CMDC.Backend.Results,所有 callback 返回 struct, 不再用{:ok, val} | {:error, reason}双形态,便于 pattern match 与 序列化(Postgres / JSON)。 - 标准错误码 atom:
:file_not_found / :permission_denied / :is_directory / :invalid_path(4 基础 + 允许 backend 扩展 string)。 - 可路由 (Composite):
CMDC.Backend.Composite支持按路径前缀路由到不同子 backend,让一个会话同时挂载 sandbox + PG memory + ETS history。
与 CMDC.Sandbox 关系
历史 CMDC.Sandbox 已经定义了类似但更简单的 8 callback(read_file/write_file/...)。
Phase 12C.0 将 CMDC.Sandbox 重构为 extends CMDC.Backend,只新增 execute/2
与 id/0,消除两套接口重叠。
与 CMDC.Memory 关系
现有 CMDC.Memory 是「语义记忆存储」behaviour(store/search/similarity_search),
与本模块用途完全不同,命名上独立,不冲突。
Quick Start
backend = CMDC.Backend.State.new(:my_table)
%CMDC.Backend.Results.WriteResult{path: "/hello.txt"} =
CMDC.Backend.write(backend, "/hello.txt", "world")
%CMDC.Backend.Results.ReadResult{file_data: %{content: "world"}} =
CMDC.Backend.read(backend, "/hello.txt")实现指南
实现 backend 时只需 use CMDC.Backend(引入默认 read/3 等 dispatcher)+
实现 callback。最少要实现 ls / read / write 三件套,其他可逐步补齐。
defmodule MyBackend do
use CMDC.Backend
@impl true
def read(backend, path, opts), do: ...
# 其他 callback ...
endBackend Factory
支持运行期注入:传递工厂 {module, opts} 或 lambda runtime -> backend.t()。
Summary
Types
Backend 工厂 — 接受 runtime(context)返回 backend 实例。
callback 通用选项。
路径 — 必须以 / 开头的绝对路径。
Backend 实例 —— 通常是 struct,由 Backend.State.new/1 等构造器返回。
Callbacks
批量下载二进制文件。
glob 文件匹配。
搜索文件内容(literal substring,不是 regex)。
列出指定目录下的直接子项(非递归)。
读取文件内容,支持按行分页。
批量上传二进制文件。
创建新文件。文件已存在时返回 :invalid_path 错误(用 edit/4 修改既有文件)。
Functions
委派到 backend.download_files/2。
委派到 backend.edit/4,opts 可选。
委派到 backend.glob/3,opts 可选(含 :path)。
委派到 backend.grep/3,opts 可选。
委派到 backend.ls/1。
委派到 backend.read/3,opts 可选。
解析 backend factory — 接受 struct 或函数。
委派到 backend.upload_files/2。
委派到 backend.write/2。
Types
Callbacks
@callback download_files(t(), paths :: [path()]) :: [ CMDC.Backend.Results.FileDownloadResponse.t() ]
批量下载二进制文件。
返回与输入等长的 [%FileDownloadResponse{}],支持 partial success。
@callback edit(t(), path(), old_string :: String.t(), new_string :: String.t(), opts()) :: CMDC.Backend.Results.EditResult.t()
精确字符串替换。
选项
:replace_all—true替换全部、false要求唯一匹配(默认false)
@callback glob(t(), pattern :: String.t(), opts()) :: CMDC.Backend.Results.GlobResult.t()
glob 文件匹配。
选项
:path— 基础目录(默认/)
@callback grep(t(), pattern :: String.t(), opts()) :: CMDC.Backend.Results.GrepResult.t()
搜索文件内容(literal substring,不是 regex)。
选项
:path— 搜索目录(默认 cwd):glob— 文件名过滤
@callback ls(t(), path()) :: CMDC.Backend.Results.LsResult.t()
列出指定目录下的直接子项(非递归)。
返回 %LsResult{entries: [%FileInfo{}]},目录条目以 / 结尾、
:is_dir 为 true。
@callback read(t(), path(), opts()) :: CMDC.Backend.Results.ReadResult.t()
读取文件内容,支持按行分页。
选项
:offset— 起始行(0-indexed,默认 0):limit— 最大行数(默认 2000)
@callback upload_files(t(), files :: [{path(), binary()}]) :: [ CMDC.Backend.Results.FileUploadResponse.t() ]
批量上传二进制文件。
返回与输入等长的 [%FileUploadResponse{}],支持 partial success。
@callback write(t(), path(), content :: String.t()) :: CMDC.Backend.Results.WriteResult.t()
创建新文件。文件已存在时返回 :invalid_path 错误(用 edit/4 修改既有文件)。
Functions
@spec download_files(t(), [path()]) :: [CMDC.Backend.Results.FileDownloadResponse.t()]
委派到 backend.download_files/2。
委派到 backend.edit/4,opts 可选。
@spec glob(t(), String.t(), opts()) :: CMDC.Backend.Results.GlobResult.t()
委派到 backend.glob/3,opts 可选(含 :path)。
@spec grep(t(), String.t(), opts()) :: CMDC.Backend.Results.GrepResult.t()
委派到 backend.grep/3,opts 可选。
@spec ls(t(), path()) :: CMDC.Backend.Results.LsResult.t()
委派到 backend.ls/1。
@spec read(t(), path(), opts()) :: CMDC.Backend.Results.ReadResult.t()
委派到 backend.read/3,opts 可选。
解析 backend factory — 接受 struct 或函数。
- struct → 直接返回
- 1-arity function → 用
runtime调用并返回结果
@spec upload_files(t(), [{path(), binary()}]) :: [ CMDC.Backend.Results.FileUploadResponse.t() ]
委派到 backend.upload_files/2。
@spec write(t(), path(), String.t()) :: CMDC.Backend.Results.WriteResult.t()
委派到 backend.write/2。