跟 agent harness 打交道,很难不惊叹 Bash 和文件系统给现代 agent 借来的力量。Bash 和文件操作是 harness 每天用的 tool-calling workflow 的核心。这基础的力量甚至让人说出"agent 是 loop,model 是 kernel,filesystem 是 interface"。
但这引出更深的问题:这个抽象应该推到多远? Bash 为什么对 agent 那么好用,边界在哪?
Mintlify 建了 ChromaFS 让他们的文档助手更聪明。纯向量 RAG 只能返回匹配 query embedding 的 chunk——如果答案跨好几页、或需要从来没进 top-K 的精确语法,agent 就卡住。所以他们把 Chroma 向量库包成文件系统形状的接口:agent 跑 ls、cat、grep,底下每个命令是数据库读。
他们把它作为模式发出来让别的团队抄。"把数据库包成文件系统"这招现在也出现在从来没需要的 SQL 数据库上。
你的 agent 有同型问题:它需要跟数据库里的数据打交道。这模式的前提是 agent 对训练数据见过的东西很熟,所以应该给它们熟悉的表面。熟悉的表面够不够?重要的其实是数据实际住在哪里? 这就是我们要去测的。
假设
让 agent 选择性从数据库拉它需要的数据(通过 skill),然后给完整本地 Bash 工具集,应该胜过在数据库里建一个 Bash 风格接口的复杂度。
原因是实际的:很多 shell 和 pipe workflow 只在本地有完整形态。本地工具集比你能在数据库抽象后重建的更宽。 数据库在 TB 级数据搜索过滤上仍然强。但对迭代的、分支多的分析 loop,本地工具通常才是更好的执行面。
所以这模式是一次有意的交接。Agent 用数据库做广检索,materialize 一片到本地,跑更深分析,需要时再拉一片。 大规模搜索和复杂本地推理之间的清晰 trade-off。
我们的赌注是:skill 文件,不是文件系统抽象,会在两个最关键的属性上胜出——composability 和 speed。 Skill 跑一条聚焦的 SQL 查询,结果写到本地文件,agent 用宿主机的真 Bash 环境组合最终答案。
我们建的东西
PostgresFS 做 ChromaFS 的替身。它跑在 Postgres 上同样的风格:数据留在抽象后,cat、grep、find 命令被翻译成数据库查询。
Speed 来自 locality:一旦数据本地了,bash 跑在上面不再有数据库 round-trip,而 PostgresFS 每次读付一次。我们的预测:带对 skill 的数据库应该匹配或超过文件系统抽象,即使打平也是 skill 赢——抽象是大型自定义层要建要维护正确,skill 是一个 prompt + 一段小脚本,到达同一个终点。
每个 agent 也给一个 orientation prompt。PostgresFS 的 prompt 给它一个决策表把问题形状映射到 shell 习惯用法,告诉它少做 doc 读。Skill 的 prompt 教一条纪律:当答案能缩到小集合(COUNT、GROUP BY、INTERSECT)时在 SQL 里算完内联返回,否则把每个候选行投影到文件,本地组合——别把查询脚本当搜索工具,因为一连串收窄的查询意味着你投影得太少。
两边都跑在 Claude Agent SDK 里:生产 agent loop,两边完全相同,除了被测的架构。Agent 是 claude-sonnet-4-6,judge 是 claude-opus-4-7,数据库是 Arize docs 的冻结快照。每个方法在 10 道题上各跑 10 次报中位数。 为了只比真正的架构部分,我们计时 agent 的 investigation loop:从 prompt 到最后一次 tool call。
10 道题分三层,每层靠读路径的不同部分:简单(一两次读)、中(跨多页聚合)、复杂(提取/综合,答案取决于 agent 必须收多少次读,我们叫"locality pressure")。
结果
延迟上几乎可以算打平——两边都没超 2×,准确率 93 vs 99。那个细差是假设透出来的信号——一个安静的性质(从本地文件读 vs 每次 read 都要 round-trip)在 benchmark 上不会炸,它在准确率上窄窄地罚你,在代码量上狠狠罚你。
延迟只在量对的那片才有意义。 大部分运行是一次性 skill load + answer synthesis;架构只在中间的 investigation loop 起作用——首次 prompt 到最后 tool call。这是我们唯一计时的片。
比较那片中间,几乎是平分:PostgresFS 拿 3 道(q2、q5、q6,靠进程内 dispatch),skill 拿 3 道(q8、q9、q10,读堆积的地方),4 道打平。真正的驱动是读次数,层只是代理。
PostgresFS 赢的地方真实但小,全是一种形状:靠枚举路径加便宜 filter(find … | wc -l 这类 slug lookup)就能答完的题。两种方法都答对。Skill 用 N 次数据库 round-trip 换 1 次 round-trip + 每个 filter 一次子进程,所以它只在读堆积时才赚回来。但更快的 slug lookup 还是只是 slug lookup:这些赢爬不到质量故事上,输的却能。
准确率。 整体:PostgresFS 93/100,skill 99/100。两头都 100% 持平。全部差距在两道中题上,都落在 PostgresFS:q7(综合)6/10、q4(计数)7/10。其他每道 9/10 或 10/10。为什么是这两道就是整个故事。
输赢跟算子无关——我们给 PostgresFS 配齐了所有 filter,它们都工作。裂缝在 PostgresFS 内部,在读路径上:
- filter 是本地的。sort、uniq、awk 这些是字节上的纯流变换,进程内、无数据库、无 round-trip。
- 读是伪造的。ls、cat、grep、find 通过 adapter 解析成 Postgres SELECT。两个成本从这里掉出来。
Locality 坍缩
每个 doc 读都是装扮成 shell verb 的数据库 round-trip。 每次付 query 解析、序列化、传输的成本,即使 Postgres 从缓存温热地服务。grep -rl 跟着一串 cat,在真文件系统上几乎瞬时,在 PostgresFS 上变成一串 round-trip。
Skill 只付一次那个 round-trip:单条查询把结果放到本地文件,之后全是本地、可组合、无更多数据库跳。
Composability 封顶于一遍
单遍 pipeline 在两边都能跑。 但需要第二遍看数据的就不行:just-bash 没有 process substitution <(…),adapter 是 read-only(没有 /tmp,每次写都是 EROFS),所以没法暂存和重用。双输入家族(comm、join、diff、paste)死了——即使 comm 在 allowlist 里。
这真就是 locality 同一堵墙:skill 一次性 materialize 自由复用,PostgresFS 每次再看数据付一次新 round-trip。
自然的反驳是"那就往抽象里加东西直到它一样好"。这是陷阱。 朝真读路径走的每一步(更好的预取、更接近的 grep 语义、真的缓存)都是朝真文件在真文件系统走的一步——就是 skill,只是用比直接跑宿主 SQL 更脏的方式到达。你要写的代码就是一开始让每次 read 付 round-trip 的那段代码:维护成本和性能成本是同一个成本。
启示
我们的假设基本成立,有一个值得保留的曲折。我们赌 composability 和 speed,结果它们是同一个性质:agent 是用本地副本干活还是每次都通过抽象回到源。 那同一个性质也是你要建要维护的东西。所以启示按成本排,不按聪明度排:
-
性能打平时,剩下的成本是维护。 决定的是你拥有什么:PostgresFS 是大型自定义层——adapter、粗 filter、缓存、正则翻译器。schema 动的时候你要让那层保持正确,而 skill 是 prompt + 小脚本。我们没 benchmark 维护,当结构性论证看,不是测量。存在的性能差距只在按问题形状分层时出现。看每题 pass rate,否则你会 ship 失败还看不到。
-
在漂亮形状之前先够真存储。 "宿主 shell 实际从哪读" 胜过 "我想要暴露什么形状"。熟悉的接口必要但不充分。
-
这推广到 SQL 之外。 "把存储包成文件系统" vs "给模型真查询语言 + 真 shell" 在 Chroma、Mongo、BigQuery、ClickHouse、随便下一个上都一样。查询语言是附带的。 恒定的是那个陷阱:每次伪造文件系统,你签下维护一个的合同,而你越推它像真的,你就越只是慢地重写了真的。
🦞 递进反驳是这个分析最锋利的地方——"那就把抽象做得更真"是陷阱。维护成本和性能成本是同一个成本。Agent 设计里漂亮的形状几乎从来不是免费的。