给你的垂直 Agent 吃下文件系统这颗药

---
title: "给你的垂直 Agent 吃下文件系统这颗药"
author: "Peter Wang (@BrainsAndTennis)"
source_url: "https://x.com/BrainsAndTennis/status/2067345406699393176"
published_at: "2026-06-17T20:35:28.000Z"
fetched_at: "2026-06-18T15:13:18Z"
updated_at: "2026-06-18T15:13:18Z"
language: "zh"
review_status: "draft"
---

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

# 给你的垂直 Agent 吃下文件系统这颗药

“文件系统加 bash 就够了”至少已经是过去六个月构建 agent 的事实标准。如果从 [Manus](https://manus.im) 主流出圈算起,时间还更长。可问题是,文件系统和 bash 在真实垂直场景里到底给你带来什么,具体案例仍然少得可怜。所以我会讲讲我们如何为 [Shortcut](https://shortcut.ai/) 打通一个用例:数据补全。

![](https://pbs.twimg.com/media/HLCoKU4aUAAX5-b.jpg)

你有一张表,行是实体(公司、候选人、销售线索等等),还有一组你希望填上的列:一家公司的员工数和融资阶段,一个候选人的学历和代表作品,一个投资人的近期交易。数据补全就是去网页和外部来源里找这些事实,记录每条事实来自哪里,再把它们写回表里。

人们愿意为这件事付很多钱。销售团队会用数据补全销售线索,判断该联系谁、开场该说什么;招聘人员会补全候选人名单;投资人会补全交易 pipeline 并做尽调,等等。[Clay](https://www.clay.com) 本质上就是电子表格驱动的数据补全,它在 2025 年 8 月以 31 亿美元估值融资 1 亿美元。

一句话版:Shortcut 解决数据补全,不是靠设计一个专门的数据补全工具,而是把它已经有的工具 filesystem-pilling 了。

### **吃下文件系统这颗药的基础知识**

先讲基础。Filesystem-pilling 指的是给 agent 一个真正的文件系统和 shell,并让它通过磁盘上的文件来推进工作,而不是走其他方式。数据落到文件里,agent 用 bash 读写它们,流经 transcript 的主要是指向这些数据的指针,而不是数据本身。直接好处是:接收输入和生成输出时都省 token。举三个例子:

**Bash**

Bash 强大,正是因为 agent 什么都能跑;但这也意味着你没法控制输出。同一个命令可能打印 5 行,也可能打印 5000 行。所以不要试图提前把它框死,让它跑起来,再接住溢出的部分。输出太大塞不进 context 时,不要把它贴回 transcript;让它流到磁盘,模型只拿到截断后的开头和一个路径。[pi](https://github.com/earendil-works/pi) 的 [bash tool](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/tools/bash.ts) 是一个干净的参考:它把输出限制在 50 KB / 2000 行,溢出时追加完整路径。

```bash
$ grep -c ERROR app.log        # 240k matching lines
... (first ~50k chars) ...
Full output: /tmp/pi-bash-a3f9.log
```

**MCP**

大多数 MCP 工具输出都是怪物。2 MB 深度嵌套 JSON。所以别让它进 context。把它持久化到磁盘,再用 JSON parser 返回一个路径和数据形状,而不是返回具体值。有了形状和几条样例记录,模型就可以直接去取真正想要的值:用 jq 抽它需要的两个字段,过滤出匹配的 12 行,再统计剩下的,而不是读完 4182 个联系人只为了用其中几条。

```bash
result saved to /mcp/contacts-8f21c0.json (2.1 MB).
JSON shape:
{ "contacts": [ { "id": string, "name": string, "email": string, "company": string } ] (4182 items) }
```

**Subagents**

假设你有一个非常适合委派给 subagent、并行度很高的任务,比如研究 200 家公司,找出它们最新一轮融资、员工数和主要产品。天真的做法是把一整段自然语言 brief 塞进每一次 subagent 调用里,于是在父 agent 的输出中,把几乎相同的一段话重新膨胀 200 次。修法只需要给 subagent tool 做一个小改动:让它的 query 可以接收一个 .txt 文件,而不只是内联字符串。一旦 query 是文件,agent 就可以用 bash 以编程方式生成全部 200 份,而不是手写出来。

```bash
jq -c '.[]' companies.json | while read -r c; do
name=$(jq -r '.name' <<<"$c"); domain=$(jq -r '.domain' <<<"$c")
cat > "briefs/${domain}.txt" <<EOF
Research ${name} (${domain}). Find: latest funding round and amount,
employee count, primary product, and any acquisitions in the last 2 years.
Cite a source URL for each fact.
EOF
done
```

这三个例子里跑的是同一个动作:把大块内容留在模型外面,让文件来承载它。这样更快、更便宜;那些没有花在大块内容上的 token,就能留给推理。context 越瘦,agent 越聪明。

### **案例研究:数据补全**

上面这些基础做法对任何 agent 都有用。但 filesystem-pilling 放到一个定制的垂直场景里,比如数据补全,仍然划算吗?

**显而易见的解法**

从机械流程看,数据补全永远是同一个循环:从一张表开始,通过外部研究填上缺失列,附上来源,规范化答案,导出。

显而易见的解法会照着表格网格来:每个单元格里放一个 agent。[Paradigm](https://www.paradigmai.com) 做的是 AI-native spreadsheet,每个单元格里都有一个 agent,负责搜索网页并填入自己的值;[Clay](https://www.clay.com) 按行补全;[Quadratic](https://www.quadratichq.com)、[Freckle](https://www.freckle.io) 等公司也各自做了同一思路的变体。执行模型就是 UI 本身:

```markdown
for each row:                            4,000 rows
for each missing column:               × 3 columns
async spawn an agent for this cell     = 12,000 agents
```

演示起来很好看——单元格一个接一个亮起来——而且产品抽象很干净。但它也只解决一种形状的问题。拿一张真实表格来看:

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

这张表里的每一列都会破坏单元格模型,而且破坏方式各不相同:

- 标签不是扁平的。*Comp → Base* 和 *Comp → Equity* 是一个逻辑概念,被嵌套表头拆成了两部分。一个只按单个 *(row, column)* 标签取值的工具,没地方表达这种层级,于是两个单元格会被当成互不相关的问题来研究。
- 标签不等于查询。“Notable projects of B. Okoro” 是一个很糟的搜索 query;“open-source projects and conference talks by B. Okoro, the candidate who led a payments rewrite” 才是好 query——但组合出这个 query 需要同一行里的其他单元格,而单元格 agent 永远看不到它们。
- 独立回答不会自动标准化。往任意一列向下看,*highest degree* 这一个事实会出现四种格式。每个单元格 agent 都是孤立作答,所以没有任何东西看过整列,也就没人把它统一起来。

这些不是几个彼此独立的 bug,而是同一个根因:cell-agent 工具把 agent 的智能限制在“行”ד列”×精确标签格式里。研究问题本来不是单元格形状的,但工具强迫它变成单元格形状。

**修法:把一个普通网页搜索 filesystem-pill 掉**

修法不是做一个更好的数据补全工具,而是不做这个工具。我们转而把一个普通网页搜索 filesystem-pill 掉。

Shortcut 的 *web_search* 只做一件朴素的事:接收一个 .txt 文件,里面每行一个 query,再接收一个用于整个批次的输出 schema;它并发运行这些 query,然后每个结果写一行 JSONL。它完全不知道什么是电子表格、行或单元格。所以 agent 用代码来组合这项任务:

```python
rows = json.load(open("input_rows.json"))
with open("queries.txt", "w") as f:
    for r in rows:
        f.write(f"Highest degree, institution, total years of professional experience, "
                f"and notable projects of {r['name']}, {r['title']} at {r['company']} "
                f"({r['linkedin']})\n")
```

这会产出一个普通文件,每行都是一个自包含 query:

```markdown
# queries.txt  (4,000 lines)

Highest degree, institution, total years of professional experience, and notable projects of A. Chen, Staff Engineer at Google (linkedin.com/in/anqichen)
Highest degree, institution, total years of professional experience, and notable projects of B. Okoro, Principal Engineer at Stripe (linkedin.com/in/bokoro)
...
```

然后 agent 把它交给工具:

```json
web_search({
  inputPath: "/workspace/enrichment/queries.txt",        // 4,000 lines
  outputSchema: { type: "object", properties: {
    highest_degree:   { type: "string" },
    institution:      { type: "string" },
    years_experience: { type: "number" },
    notable_projects: { type: "array", items: { type: "string" } }
  }, required: ["highest_degree", "years_experience"] }
})
```

这几行代码直接修掉了三个失败里的两个。三列被折叠成对每个人的一次查询,所以每一行都变成了真正的 query:姓名、职位和公司被编织在一起,而不是一个裸露的单元格标签。而且一个 schema 应用到整个批次,一次性钉住每一行的答案形状。这相当于查询次数省了 3 倍。

```json
{"line":1,"status":"found","answer":{"highest_degree":"PhD","institution":"Stanford University","years_experience":11,"notable_projects":["Spanner","F1"]},"sources":[{"url":"..."}]}
{"line":2,"status":"found","answer":{"highest_degree":"MS","institution":"MIT","years_experience":7,"notable_projects":["..."]},"sources":[{"url":"..."}]}
{"line":3,"status":"not_found","answer":null,"sources":[]}
```

第三个失败,也就是标准化,交给下一步处理。结果文件回到发起搜索的同一个 agent loop;这时它带着整列视野来读结果:把 *PhD* 和 *Doctorate* 合并成一种形式,把一个跑偏的 *"8 years"* 强制转成 *8*,对返回太薄的项重新发起查询,然后不断迭代,直到这一列从结构上就是一致的。这一步是单元格模型在结构上不可能拥有的,因为没有任何 cell agent 能看到自己的单元格之外。

而且因为每一步都留下了文件,整个工作流获得了两个属性:可重跑,可审计。可重跑:后续阶段需要修正时,可以重新处理保存下来的 JSONL,而不是再跑一遍昂贵的网页研究。可审计:每个中间产物——组合出的 query、原始结果、片段,甚至被拒绝掉的来源——都是用户可以阅读、也可以下载的真实文件。

### **教训**

面对电子表格补全这样的垂直场景,本能反应是做一个电子表格专用工具:每个单元格一个 agent,一个 enrichment engine,把网格烘进产品里。它演示效果好,也覆盖常见情况。

不要做那个垂直工具。把垂直问题转成通用问题,让 agent 自己组合 pipeline。边缘情况就不再是特例。同一组处理过数据补全的 primitives,也能处理下一个垂直场景,因为其中没有任何东西是关于数据补全本身的。

找到那个你正准备构建定制工具的位置,然后问:它的通用版本是什么?比你想象中更常见的情况是,你能为一个垂直场景构建的最好东西,恰恰是那个完全不针对它的东西。