为你的 Agent 伪造一个文件系统:PostgresFS

---
title: 为你的 Agent 伪造一个文件系统:PostgresFS
author: Aparna Dhinakaran (@aparnadhinak, founder @arizeai) co-written with @SufjanFana
published_at: 2026-06-03
source_url: "https://x.com/aparnadhinak/status/2062233330196926720"
language: zh
review_status: draft
fetched_at: 2026-06-04
updated_at: 2026-06-06
---

**为你的 Agent 伪造一个文件系统:为什么 PostgresFS 败给了一个 skill**

*与 @SufjanFana 合著*

做 agent harness 久了,很难不被 Bash 和文件系统为现代 agent 带来的力量打动。Bash 和文件操作是 harness 每天用的 tool-calling 工作流的核心。这个基础之强,甚至让一些人喊出了 ["Bash 可能就是你需要的全部。"](https://vercel.com/changelog/introducing-bash-tool-for-filesystem-based-context-retrieval)

但这在我们内部绕不过一个更深的问题:这条抽象该推多远?Bash 对 agent 为什么这么好使,边界又在哪里?

[@mintlify 最近推出了一种叫 ChromaFS 的方案](https://www.mintlify.com/blog/how-we-built-a-virtual-filesystem-for-our-assistant),给他们的数据库套了一层类文件系统接口。那篇文章本身深思熟虑,也在我们团队内部引发了认真讨论:每个数据库都该向 agent 暴露这种接口吗?这恰好是检验我们对 Bash 中心工作流深层疑问的最佳测试案例。

![](https://pbs.twimg.com/media/HJ6DxVAa0AASBYM.jpg)

## **团队正在复制的模式**

**Mintlify 构建 ChromaFS 是为了让文档助手更聪明。** 普通向量 RAG 只能返回与查询 embedding 匹配的片段——如果答案横跨好几页、或者需要的精确语法没进 top-K,agent 就卡住了。于是他们把 Chroma 向量库包上了一层文件系统形状的接口:agent 跑 ls、cat、grep,而每条命令底下都是对数据库的一次读取。他们把这个方案公开成其他团队可以复制的模式。"把数据库披成文件系统"这一招,现在正出现在根本不需要它的 SQL 数据库上。

你的 agent 面临的是同一个形状的问题:它需要跟数据库里的数据打交道。这套模式的出发点是:agent 对训练数据见过的东西已经很熟,所以应该递给它们一个 *familiar surface*。一个熟悉的界面够不够,还是数据实际存在哪儿才重要——这正是我们决定测试的命题。

## **我们的假设**

只让 agent 通过一个 skill 从数据库里精准拉取它需要的数据,再给它完整的本地 Bash 工具集,应该比把一套 Bash 接口硬塞进数据库本身的复杂度更划算。

原因是务实的:很多 shell 和 pipe 工作流只有在本地才以完整形态存在。本地工具箱的丰富度,是数据库抽象很难真正复现的。数据库在这里仍有一个长项:跨 TB 级数据的搜索和过滤。但对迭代性强、分支多的分析循环,本地工具通常是更好的执行面。

所以这套模式是有意的交接。agent 用数据库做大范围检索,物化一份数据到本地,再跑深度分析,需要时再拉另一份。大规模搜索和复杂本地推理之间就有了一个干净的取舍。

我们的赌注是:一个 skill 文件,而不是文件系统抽象,会在两项最关键的性质上胜出:**可组合性** 和 **速度**。skill 跑一条聚焦的 SQL 查询,把结果写到本地文件,agent 再用宿主真正的 Bash 环境组合出最终答案。

作为 ChromaFS 的替身,我们用 PostgresFS 来测。它跑在 Postgres 之上,风格一致:数据留在抽象层之后,cat、grep、find 这类命令都被翻译成数据库查询。

**速度**来自局部性:数据一旦在本地,Bash 在其上运行就不需要再来一次数据库往返;而 PostgresFS 每次读取都要付一次。**可组合性**是同一性质的另一面:任何需要对数据做第二遍处理的东西(暂存中间结果、使用双输入操作符如 comm 或 join),都需要一个可写、可再读的本地归宿,只读抽象给不了。两者都落到同一个问题上:agent 是持有一份自己所有的数据本地副本,还是每次读取都从抽象层往回探?

所以我们的预测是:配备合适 skill 的数据库应该持平或胜过文件系统抽象,就算平手也是 skill 赢——抽象是个大型自定义层,造出来还得一直维护正确;而 skill 只是一个 prompt 和一小段脚本,到达同一个地方。

## **PostgresFS 还是一个 skill**

两个都做了,决定拿它们和我们对生产 Arize AX 文档的 Postgres 快照做线上实测。以下是两者的真实样貌:

- **PostgresFS,文件系统抽象。** 五个 ChromaFS 动词(ls、cat、grep、find、cd)对应虚拟路径解析为 Postgres 读取,外加标准 coreutils 过滤器(sort、uniq、wc、awk、sed、cut、tr、head、tail、comm)。agent 像逛代码库一样探索文档。接入方式仿照 Mintlify 的 ChromaFS:一个进程内 shell(just-bash)注册为 agent 的 Bash tool。测的是真模式,不是稻草人。只读:五个动词变成 SELECT,过滤器在拿回字节后本地运行。
- **The skill,SQL 工作流。** 没有抽象。agent 获得宿主真正的 bash shell,外加一小段脚本,接收一条 SQL 查询并写结果到本地文件。它学会的工作流:写查询、执行、用真实的 grep / jq / sort / pipes 在文件上组合答案。agent 做翻译;runtime 只搬运字节。

两者实现的是同一件事:PostgresFS 每次读都打回 Postgres,skill 只去一次数据库,其余全在本地完成。

![](https://pbs.twimg.com/media/HJ6D5XQboAAN3t_.jpg)

每个 agent 还拿到一份引导提示词。PostgresFS 的 prompt 给出一张问题形状到 shell 用法的决策表,叮嘱它减少文档读取次数。Skill 的 prompt 教一种纪律:能用 SQL 直接算出答案的(COUNT、GROUP BY、INTERSECT)就内联返回;否则把每条候选行投影到文件再本地组合——*不要* 把查询脚本当成搜索工具,因为一连串不断收窄的查询意味着你查得过于局限。

## **测试方法**

两条实验臂都在 Claude Agent SDK 内部跑:生产 agent loop,两侧相同,被测架构之外完全一致。Agent 是 claude-sonnet-4-6,judge 是 claude-opus-4-7,数据库是 Arize 文档的冻结快照。每条方法跑 10 个问题各 10 次,取中位数报告。为只比较真正的架构部分,我们测量 agent 的**调查循环**:从 prompt 到最后一次 tool call。评分是混合的:答案精确的用程序判断(slug 集合、计数),综合题用固定 rubric 下的 LLM judge。

十个问题分三档,每档侧重读路径的不同部分:**简单**(一次或数次读取)、**中档**(跨多页聚合)、**复杂**(提取或综合,答案取决于 agent 必须把多少独立读取拼在一起,我们称之为**局部性压力**):

完整题目列表:

![](https://pbs.twimg.com/media/HJ6EJyvbgAATSXA.png)

**结果**

纸面上接近得可以算平局:延迟没有哪方赢出 2 倍,准确率落到 93 对 99。这么薄的边距正是假设的投影——从本地文件读而非每次读都往返,这一条安静的性质不会把 benchmark 掀翻;它以窄幅的准确率代价、重度的代码代价跟你计价。

**延迟只在测量对的切面上才计数。** 一次运行大部分是一次性的 skill 加载和答案综合;架构只作用于两者之间的调查循环——从第一个 prompt 到最后一个 tool call。量化的就是这个时间段。下面是 q7 内部的情况:

![](https://pbs.twimg.com/media/HJ6ETBFbEAAzHUj.jpg)

十个问题跨这个中间切片对比下来近乎均分:PostgresFS 拿三题(q2、q5、q6)在进程内分发上占优;skill 拿三题(q8、q9、q10)在读取堆积上胜出;四题平手。真正的驱动因素是读取次数;tier 只是这个因素的代理。各题中位数:

![](https://pbs.twimg.com/media/HJ6FD5wb0AAUNqy.png)

按 tier 汇总(同一 tier 所有轮次的中位数):

![](https://pbs.twimg.com/media/HJ6FYSMbkAEfhx3.png)

**PostgresFS 的胜出**是真实但微小的,而且全是同一种形状:靠枚举路径然后用 find … | wc -l 这种便宜过滤器收尾的问题,slug 查找。两边都能答对这些。Skill 用一次数据库往返加每个过滤器一个子进程,替代 N 次往返,所以只有一旦读取堆积起来才回本。但更快的 slug 查找终究只是一个 slug 查找:这些胜出堆不出质量优势,但败局可以。

**准确率。** 总计:**PostgresFS 93/100,skill 99/100。** 两端都在 100% 打平。差距全来自两个中档题,都在 PostgresFS 上:**q7(综合题)6/10** 和 **q4(计数题)7/10。** 其他每题两边都是 9/10 或 10/10。为什么是这两题——就是整个故事。

![](https://pbs.twimg.com/media/HJ6FLY1bYAAk9DN.png)

## **为什么 PostgresFS 输了**

败因不在缺少操作符:我们给了 PostgresFS 每一个过滤器,而且它们都能正常工作。分歧在 PostgresFS 内部,在读路径上:

- **过滤器在本地。** sort、uniq、awk 这几位是对 pipe 里已有的字节做纯流变换:进程内、不需要数据库、也没有往返。
- **读取其实是模拟的。** ls、cat、grep、find 经由一个适配器把每条命令转成一条 Postgres SELECT。这引出两项代价。

**局部性塌缩。** 每次文档读取都是一次套着 shell 动词外壳的数据库往返。每次都要付查询解析、序列化、传输的成本,即便 Postgres 在热缓存里已经命中。grep -rl 加上一阵密集的 cat,在真实文件系统上瞬间完成,变成了一趟趟往返。Skill 只付一次往返:一条查询把结果落到本地文件,后面全是本地的、可组合的,不需要更多数据库跳转。

**可组合性,卡在单次通行。** 单次 pass 的管道两边都行。但需要对数据做第二遍处理的东西就不行了:just-bash 没有进程替换 `<(...)>`,适配器又是只读的(没有 /tmp,每次写都是 EROFS),所以没有任何东西能被暂存并复用。双输入家族(comm、join、diff、paste)完全无法使用,哪怕 comm 还在白名单里。这和局部性是同一堵墙:skill 物化一次后自由复用,PostgresFS 每次看数据都要付一次全新的往返。

天然的反驳是"那就继续往抽象里加东西直到一样好"。这是陷阱。往忠实读取路径迈的每一步(更好的预取、更接近 grep 的语义、真正的缓存)都是往真实文件和真实文件系统迈的一步——那正是 skill,只是走得不比直接跑 SQL 更干净。而你为了到达那儿写的代码,正是最开始让每次读都往返的那套代码:**维护代价和性能代价是同一个代价。**

## **这意味着什么**

假设大体成立,只有一个值得保留的褶皱。我们押的是可组合性 *和* 速度;它们最后证明是一个性质:agent 是从自己所有的数据本地副本上工作,还是每次读都从抽象层往回探。这个性质也正是你要去造和去维系的。所以结论按代价而非聪明程度排:

1. **性能持平时,剩下的代价是维护。** 真正决定胜负的是你拥有什么:PostgresFS 是大型自定义层的组合——适配器、粗过滤器、缓存、正则翻译器。schema 变动时你得保证这层一直正确;skill 只是一个 prompt 和一小段脚本。我们没有对维护做基准测试,所以把它当结构论证看待,不是测量。而且真正存在的性能差距只在**按问题形状分层**时才会显现。看每题通过率,否则你会把失败发布出去却浑然不知。
2. **先伸手去摸真实的存储,再考虑漂亮的面。** "宿主 shell 实际读的是什么?"比"我想暴露什么形状?"更实在。熟悉的面是必要但不充分的。
3. **这超越 SQL 可泛化。** "把存储包装成文件系统"对比"给模型真正的查询语言加真正的 shell",对 Chroma、Mongo、BigQuery、ClickHouse 或下一个东西都是同一个决定。查询语言是非本质的。一直不变的是这个陷阱:每次你伪造一个文件系统,就签了一份维护合同;而你把它往真的越逼越近,也只是更慢地重造了真实的系统。

## **下一步**

更大的图景是:用 Arize AX 做 evals 能让你对自己的架构选择从猜测变成确知。如果你觉得这听起来靠谱,[今天就试试我们](https://arize.com)。