@aparnadhinak: https://x.com/aparnadhinak/status/2062233330196926720
摘要
一个Twitter讨论串,探讨了数据库文件系统抽象(PostgresFS)和基于技能的方法(使用本地 Bash)哪个更适合代理工作流。技能方法在组合性和速度上胜出。
查看缓存全文
缓存时间: 2026/06/03 21:54
为你的 Agent 伪造一个文件系统:PostgresFS
为你的 Agent 伪造一个文件系统:为什么 PostgresFS 输给了技能
与 @SufjanFana 合著
在与代理工具框架打交道时,很难不被 Bash 和文件系统赋予现代代理的强大能力所折服。Bash 和文件操作是框架日常使用的工具调用工作流的基石。这个基础之稳固,甚至让一些人说出:“Bash 可能就是你所需要的一切。”
但这提出了一个更深层次的问题:我们应该将这个抽象推向多远?为什么 Bash 对代理如此有效,它的边界又在哪里?
@mintlify 最近介绍了一种称为 ChromaFS 的方法,它为他们的数据库提供了一种类文件系统接口。他们的文章很有见地,并在我们团队内部引发了严肃的辩论。是否每个数据库都应该为代理暴露出一个类文件系统接口?这感觉像是一个完美的测试案例,用来回答我们关于以 Bash 为中心的工作流的一些更深层次的问题。
团队正在复制的模式
Mintlify 构建了 ChromaFs,使他们的文档助手更智能。 纯向量 RAG 只能返回与查询嵌入匹配的块;如果答案跨越多个页面,或者需要从未出现在 top-K 中的精确语法,代理就会卡住。因此,他们将 Chroma 向量存储包装成一个文件系统形状的接口:代理运行 ls、cat、grep,而每个命令底层都是数据库读取。他们将其作为一个模式发布,供其他团队复制。同样的“将数据库包装成文件系统”的做法现在正出现在那些从未需要它的 SQL 数据库上。
你的代理面临相同的问题:它需要处理数据库中的数据。该模式的前提是,代理对训练数据中见过的内容很熟练,所以你应该给它们一个熟悉的界面。我们着手测试的是:一个熟悉的界面是否足够,还是真正重要的是数据实际存在于何处。
我们的假设
有选择地让代理仅通过技能从数据库中拉取所需数据,然后为其提供完整的本地 Bash 工具集,应该能击败在数据库本身中构建类似 Bash 接口的复杂性。
原因很实际:许多 shell 和管道工作流只在本地以完整形式存在。本地工具箱比你在数据库抽象背后实际能重建的更广泛。数据库在这里仍然擅长一件事:在 TB 级数据上进行搜索和过滤。但对于迭代性强、分支多的分析循环,本地工具通常是更好的执行表面。
所以这个模式是刻意的手动交接。代理使用数据库进行广泛检索,物化一个切片到本地,运行更深入的分析,并在需要时拉取另一个切片。这在大规模搜索和复杂本地推理之间提供了清晰的权衡。
我们的赌注是:一个技能文件(而不是一个文件系统抽象)将在对该工作最重要的两个属性上胜出:可组合性和速度。技能运行一个集中的 SQL 查询,将结果写入本地文件,然后代理使用宿主真实的 Bash 环境编写最终答案。
作为 ChromaFS 的替代品,我们使用了 PostgresFS。它同样以类似方式在 Postgres 上工作:数据保留在抽象背后,cat、grep 或 find 等命令被转换为数据库查询。
速度来自于局部性:一旦数据在本地,bash 可以在不再次进行数据库往返的情况下处理它,而 PostgresFS 每次读取都要支付一次往返开销。可组合性同样是这个局部性的体现:任何需要对数据进行第二次处理的操作(暂存中间结果、使用像 comm 或 join 这样的双输入运算符)都需要一个可写、可重读的本地空间,而只读抽象无法提供这一点。两者都归结为一个问题:代理是否拥有数据的本地副本,还是每次读取都通过抽象回退?
因此我们的预测:拥有正确技能的数据库应该能够匹配或击败文件系统抽象,即使是平局也是技能的胜利:抽象是一个庞大且需要保持正确的定制层,而技能只是一个提示和一个达到相同效果的小脚本。
PostgresFS 或一个技能
我们构建了两者,并决定将它们与移植到 Postgres 中的实时 Arize AX 文档进行测试。以下是每个方案的实际内容:
-
PostgresFS,文件系统抽象。 五个 ChromaFs 动词(ls, cat, grep, find, cd)作用于解析为 Postgres 读取的虚拟路径,外加标准的 coreutils 过滤器(sort, uniq, wc, awk, sed, cut, tr, head, tail, comm)。代理像浏览代码库一样浏览文档。我们按照 Mintlify 构建 ChromaFS 的方式连接它:一个进程内的 shell(just-bash)注册为代理的 Bash 工具。所以我们测试的是真正的模式,而不是一个稻草人。它是只读的:五个动词变成 SELECT,过滤器在接收到的字节上本地运行。
-
技能,SQL 工作流。 没有抽象。代理获得宿主的真实 bash shell 加上一个小脚本,该脚本接受一个 SQL 查询并将结果写入本地文件。它学习的工作流是:编写查询,运行它,使用真正的 grep / jq / sort / pipes 针对文件编写答案。代理负责翻译;运行时只移动字节。
这两种方式做的是相同的工作:PostgresFS 将每次读取送回 Postgres,而技能只进行一次数据库往返,其余所有操作都在本地完成。
每个代理还获得一个引导提示。PostgresFS 的提示为它提供一个决策表,将问题形状映射到 shell 习语,并要求它减少文档读取次数。技能的提示教导一种纪律:当问题归结为小集合(COUNT, GROUP BY, INTERSECT)时,在 SQL 中计算答案并内嵌返回;否则将每个候选行投影到文件并在本地组合——不要将查询脚本当作搜索工具,因为一连串的缩小查询意味着你投影得太少了。
我们如何测试
两个分支都在 Claude Agent SDK 内部运行:生产代理循环,除了待测试的架构外,其他完全相同。代理是 claude-sonnet-4-6,评判是 claude-opus-4-7,数据库是 Arize 文档的冻结快照。我们在 10 个问题上各运行每种方法 10 次,并报告中位数。为了仅比较架构部分,我们计时代理的调查循环:从提示到其最后一个工具调用。评分混合进行:答案精确时进行程序化评分(slug 集合、计数),对于综合问题则使用 LLM 评判依据固定评分标准。
十个问题跨越三个层级,每个层级侧重于读取路径的不同部分:简单(一或几次读取),中等(跨多个页面的聚合),以及复杂(提取或综合,其答案取决于代理必须收集的独立读取次数,我们称之为局部性压力):
以下是完整的问题集:
结果
在纸面上,它们足够接近以至于可以称为平局:两者在延迟上都没有超过 2×,准确率在 93 到 99 之间。这个微小的差距正是假设的体现——一个安静的属性,从本地文件读取与每次读取都往返,从未使基准测试爆表;它在准确率上小幅收费,而在代码上大量收费。
延迟只有在你测量正确的切片时才有意义。 运行的大部分时间是一次性的技能加载和答案合成;架构仅在它们之间的调查循环中起作用,从第一次提示到最后一次工具调用。这是我们计时的唯一切片。这是它在 q7 内部的情况:
比较这十个问题的中间切片,几乎是均分:PostgresFS 在三个问题(q2, q5, q6)上因进程内分发而获胜,技能在三个问题(q8, q9, q10)上因读取积累而获胜,四个问题持平。真正的驱动因素是读取次数;层级只是它的代理。每个问题的中位数:
按层级汇总(每个层级所有重复的中位数):
PostgresFS 获胜的地方 是真实但微小的,并且全部是同一形状:通过枚举路径并用廉价过滤器(如 find … | wc -l,一个 slug 查找)完成的问题。两种方法都能正确处理这些。技能用 N 次数据库往返换取一次往返加上每个过滤器的子进程,所以只有在读取累积时才物有所值。但更快的 slug 查找仍然只是 slug 查找:这些胜利并没有累积成质量故事,但失败却会。
准确率。
总体:PostgresFS 93/100,技能 99/100。 两端都有 100% 的平局。整个差距来自两个中等层级的问题,都在 PostgresFS 上:q7(综合)6/10 和 q4(计数)7/10。其他每个问题在两者上都是 9/10 或 10/10。为什么偏偏是这两个问题,这就是整个故事。
为什么 PostgresFS 输了
失败并非因为缺少运算符:我们给了 PostgresFS 所有过滤器,它们都能工作。分裂在 PostgresFS 内部的读取路径上:
-
过滤器是本地的。 sort、uniq、awk 等是纯流转换,作用于已经在管道中的字节:进程内,无数据库,无往返。
-
读取是伪造的。 ls、cat、grep、find 通过一个适配器解析,每个都变成 Postgres SELECT。由此产生两个成本。
局部性崩溃。 每次文档读取都是一次数据库往返,伪装成 shell 动词。每次都要支付查询解析、序列化和传输的成本,即使 Postgres 从缓存中提供温暖数据。一个 grep -rl 后跟一串 cats,在真实文件系统上几乎是瞬间完成的,变成了一个序列的往返。技能一次性支付该往返成本:一个查询将结果落到本地文件上,之后的一切都是本地且可组合的,没有更多的数据库跳转。
可组合性,限制为一次传递。 单次传递管道在两者上都能工作。但任何需要对数据进行第二次处理的操作都不行:just-bash 没有进程替换 <(…),并且适配器是只读的(没有 /tmp,每次写入都是 EROFS),所以没有任何东西可以暂存和重用。双输入家族(comm、join、diff、paste)是死的,即使 comm 在允许列表中。这实际上与局部性是同一堵墙:技能一次物化并自由重用,而 PostgresFS 每次查看数据都要支付一次新的往返。
自然的反对意见是“那就往抽象里加东西,直到它变得足够好”。这是一个陷阱。每向更忠实的读取路径迈出一步(更好的预取、更接近的 grep 语义、真正的缓存),都是向真实文件系统上的真实文件迈出一步,也就是技能,但远不如直接在宿主自己的 shell 上运行 SQL 来得干净。而且为了达到那一步你需要编写的代码,与最初每次读取都要往返的代码相同:维护成本和性能成本是同一成本。
这意味着什么
我们的假设基本成立,但有一个值得注意的细微出入。我们赌的是可组合性和速度;结果它们是一个属性:代理是从它拥有的数据副本的本地工作,还是每次读取都通过抽象回退。同一个属性也正是你需要构建和维护的东西。所以结论围绕成本而非聪明程度展开:
-
性能持平的情况下,剩下的成本就是维护。 决定胜负的是你所拥有的东西:PostgresFS 是一个庞大的定制层:一个适配器、一个粗过滤器、一个缓存、一个正则表达式翻译器。随着模式变化,你必须保持该层正确,而技能只是一个提示和一个小脚本。我们没有对维护进行基准测试,所以把它当作一个结构性的论点,而不是测量结果。而且存在的性能差距只有在按问题形状分层时才会显现。查看每个问题的通过率,否则你会在没有看到的情况下交付失败。
-
在漂亮的形状之前,先使用真正的存储。 “宿主 shell 实际从何处读取?”胜过“我想暴露什么形状?”一个熟悉的界面是必要的,但不是充分的。
-
这一点超越了 SQL。 “将存储包装成文件系统”与“给模型存储的真实查询语言加上真实 shell”是相同的决策,对于 Chroma、Mongo、BigQuery、ClickHouse 或任何接下来的东西都是如此。查询语言是次要的。不变的是陷阱:每次你伪造一个文件系统,你就要负责维护一个,而你越把它推向像真实的东西,你越是在更慢地重建真实的东西。
接下来是什么
这里更大的故事是,使用 Arize AX 的评估可以从猜测你的架构选择变为确定无疑。如果这对你来说是个好主意,今天就试试我们。
相似文章
我刚刚为了可靠性重写了整个代理基础设施,有人也这样做吗?
作者描述了在遭遇级联故障后,使用DBOS持久化执行重写其AI代理基础设施以提高可靠性的经历,并向社区询问类似的经历、工具选择以及自建与购买决策。
@tom_doerr: 从 200,000 项技能构建代理 https://github.com/ynulihao/AgentSkillOS…
AgentSkillOS 是一个开源框架,使开发者能够从超过 200,000 项可用技能中检索和编排流水线来构建 AI 代理。
@tricalt: https://x.com/tricalt/status/2057173322924806651
一位创始人讨论了在生产环境中使用Markdown文件作为AI代理记忆的扩展挑战,突出了关于权限、多代理交互和时间查询的常见陷阱,并指出团队常常在不经意间修补这些问题的过程中,实际上是在重新构建一个更复杂的系统。
我厌倦了维护 skill.md 文件,所以构建了一个开源 CLI,通过 GitHub 仓库来创建、管理和观察技能。你可以在任何智能体的会话之间监控、追踪和共享技能,同时迭代改进/版本化它们。
一个开源 CLI 工具,通过 GitHub 仓库创建、管理和版本化智能体技能,支持跨会话的可靠共享和观察。
@ycombinator: Ardent (@ArdentAI) 让你在 TB 级规模下 <6秒 克隆任何 Postgres 数据库,让编码代理可以测试代码,工程团队可以快速上线而不用担心影响生产…
Ardent 是一款 Y Combinator 支持的工具,能在 TB 级规模下于 6 秒内克隆任何 PostgreSQL 数据库,让编码代理和开发者可以在接近生产环境的克隆副本上测试代码,而不会造成停机风险。该工具已被 Supermemory 和 Surface Labs 等公司采用。