> **项目**: gstack — Garry Tan 的开源 AI 工程工作流
> **GitHub**: https://github.com/garrytan/gstack (~90K stars)
> **作者**: Garry Tan — YC 总裁兼 CEO
> **核心主张**: 一个人 + AI = 过去 15 人工程团队的产出
---
## 零、一个让人沉默的事实
2026 年 3 月 12 日,Garry Tan 开源了 gstack。6 天内 25K+ stars,两个月内逼近 90K。
但比 star 数更惊人的是背后的工程方法论:这个项目的核心不是某种新奇的 AI 算法,而是一套**把提示词当作软件工程来管理**的架构体系。
这不是"更好的 prompt engineering"。这是**prompt 的软件工程化**。
我 clone 了这个项目,读完了 ARCHITECTURE.md、BROWSER.md、所有 SKILL.md 和核心源码。以下是 10 个让这个项目脱颖而出的工程密码。
---
## 一、Thin Harness + Fat Skills:架构的第一性原理
gstack 的设计哲学可以用一句话概括:
> **"不要把精力花在重复搭建框架层(harness)上,这一层交给成熟工具就好。真正该投入的,是用自然语言写清楚'这件事应该怎么做'的 markdown 提示词——也就是 Skills。"**
### 1.1 Harness 层(薄)
- **Claude Code** — 底层 AI 执行
- **Bun** — 运行时(编译为 58MB 单文件可执行程序)
- **Playwright** — 浏览器自动化
- **持久化 Chromium daemon** — 状态保持
这一层是"买来的",不是"造出来的"。gstack 不做模型训练、不做推理引擎、不做浏览器内核。它做的是**把这些工具粘合起来的工作流层**。
### 1.2 Skills 层(厚)
23 个专家角色,每个都是一份详细的 markdown 文件(SKILL.md),包含:
- 角色定义("你是高级工程经理,正在进行代码审查")
- 审查清单(SQL 安全、竞态条件、LLM 信任边界、枚举完整性)
- 输出格式(AUTO-FIX vs ASK)
- 工作流程(两轮审查的先后顺序)
这不是"给 AI 一段提示词"。这是**给 AI 一本岗位手册**——和一个新员工入职时拿到的手册一样详细、一样结构化。
### 1.3 为什么是 Markdown?
SKILL.md 文件是纯 markdown + YAML frontmatter。这意味着:
- **版本控制友好** — git diff 直接可读
- **跨工具移植** — Claude Code、Codex CLI、Cursor、VS Code 都能读
- **人类可编辑** — 不需要写代码就能调整 AI 行为
- **代码审查友好** — PR 中可以 review skill 的变更
**关键洞察**:当 AI 能力趋于 commoditized(商品化),**差异化的不再是模型,而是怎么把模型用对**——而"用对"的知识就写在这些 markdown 文件里。
---
## 二、持久化浏览器守护进程:从 40 秒到 100 毫秒
### 2.1 问题:传统方案的悲剧
传统浏览器自动化流程:
```
命令 1: 启动 Playwright → 启动 Chromium → 执行 → 关闭浏览器 (3s)
命令 2: 启动 Playwright → 启动 Chromium → 执行 → 关闭浏览器 (3s)
命令 3: ...
20 条命令 = 60 秒浏览器启动开销
```
更糟的是:每次关闭丢失所有状态。cookies、登录会话、localStorage、打开的标签页——全部清零。
### 2.2 gstack 的方案:Chromium daemon
```
首次调用:
CLI → 检查 .gstack/browse.json → 无服务器 → 启动 Bun.serve() → 启动 Chromium
总耗时: ~3 秒
后续调用:
CLI → 读取 state file → HTTP POST localhost:PORT → Chromium 执行 → 返回结果
总耗时: ~100-200 毫秒
30 分钟空闲 → 自动关闭 → 下次调用重新启动
```
**架构图:**
```
Claude Code gstack
───────── ──────
┌──────────────────────┐
Tool call: $B snapshot -i │ CLI (compiled binary)│
─────────────────────────→ │ • reads state file │
│ • POST /command │
│ to localhost:PORT │
└──────────┬───────────┘
│ HTTP
┌──────────▼───────────┐
│ Server (Bun.serve) │
│ • dispatches command │
│ • talks to Chromium │
│ • returns plain text │
└──────────┬───────────┘
│ CDP
┌──────────▼───────────┐
│ Chromium (headless) │
│ • persistent tabs │
│ • cookies carry over │
│ • 30min idle timeout │
└───────────────────────┘
```
### 2.3 为什么选择 Bun?
Node.js 能工作,但 Bun 更好——不是出于时尚,而是工程需要:
| 特性 | 解决的问题 |
|------|-----------|
| `bun build --compile` | 编译为 58MB 单文件可执行程序。没有 `node_modules`,没有 `npx`,没有 PATH 配置 |
| 原生 SQLite | 读取 Chromium 的 SQLite cookie 数据库直接解密,不需要 `better-sqlite3` 或 gyp |
| 原生 TypeScript | `bun run server.ts`,没有编译步骤、没有 `ts-node` |
| 内置 HTTP server | `Bun.serve()` 够快够简单,不需要 Express |
瓶颈永远是 Chromium,不是 CLI 或 server。Bun 的启动速度(~1ms)是锦上添花,编译二进制和原生 SQLite 才是决定性因素。
---
## 三、Ref 系统:绕过 DOM 的选择器革命
### 3.1 传统选择器的噩梦
传统方案给元素打标记:`data-ref="@e1"`。这会在生产环境崩溃:
- **CSP(内容安全策略)** — 很多站点禁止脚本修改 DOM
- **React/Vue/Svelte 水合** — 框架协调会剥离注入的属性
- **Shadow DOM** — 无法从外部触及 shadow root 内部
### 3.2 gstack 的方案:ARIA Tree → <span class="mention-invalid">@refs</span>
```
1. Agent: $B snapshot -i
2. Server: 调用 Playwright 的 page.accessibility.snapshot()
3. 解析 ARIA 树,分配顺序引用: @e1, @e2, @e3...
4. 为每个 ref 构建 Playwright Locator: getByRole(role, { name }).nth(index)
5. 存储 Map<string, Locator> 在 BrowserManager 实例上
6. 返回带 refs 前缀的紧凑文本输出
后续:
7. Agent: $B click @e3
8. Server: 解析 @e3 → Locator → locator.click()
```
**关键洞察**:Playwright Locators 是 DOM 外部的。它们使用 Chromium 内部维护的 accessibility tree 和 `getByRole()` 查询。**不修改 DOM,没有 CSP 问题,没有框架冲突**。
### 3.3 Stale 检测
SPA 可以在不触发导航的情况下突变 DOM(React router 切换、tab 切换、modal 打开)。gstack 在 `resolveRef()` 中执行异步 `count()` 检查:
```
resolveRef(@e3) → 获取 refMap 条目
→ count = await entry.locator.count()
→ 如果 count === 0: 抛出 "Ref @e3 已过期——元素不再存在。运行 'snapshot' 获取新引用"
→ 如果 count > 0: 返回 { locator }
```
快速失败(~5ms 开销),而不是让 Playwright 的 30 秒动作超时在缺失元素上过期。
### 3.4 Cursor-Interactive Refs (@c)
`-C` 标志查找可点击但不在 ARIA 树中的元素——样式为 `cursor: pointer`、有 `onclick` 属性、或自定义 `tabindex` 的元素。这些在独立命名空间中获得 `@c1`, `@c2` 引用,捕获框架渲染为 `<div>` 但实际是按钮的自定义组件。
---
## 四、安全模型:L1-L6 的防御纵深
### 4.1 双重 HTTP 监听器架构
当用户运行 `pair-agent --client` 时,daemon 启动 ngrok tunnel 让远程 agent 驱动浏览器。直接把完整 daemon 暴露给互联网是自杀行为。
gstack 的方案:**两个 HTTP 监听器**,不是两个路由过滤器:
| 端点 | 本地监听器 (127.0.0.1:LOCAL_PORT) | Tunnel 监听器 (127.0.0.1:TUNNEL_PORT) |
|------|-----------------------------------|-------------------------------------|
| `/health` | 公开(token 引导) | 404 |
| `/command` | Bearer root 或 scoped | 仅 scoped,命令白名单 |
| `/cookie-picker` | 公开 UI | 404 |
| `/inspector/*` | 认证 | 404 |
| `/connect` | 公开 | 公开(速率限制) |
**安全属性来自物理端口分离**:tunnel 调用者无法到达 `/health` 或 `/cookie-picker`,因为这些路径在该 TCP socket 上不存在。检查 `x-forwarded-for` 或 origin 不可靠;socket 分离是不可欺骗的。
### 4.2 Prompt Injection 防御(侧边栏 Agent)
Chrome 侧边栏 agent 有工具(Bash、Read、Glob、Grep、WebFetch)并读取敌对网页,是 gstack 最暴露于 prompt injection 的部分。防御是**分层**的:
**L1-L3 内容安全** — 数据标记、隐藏元素剥离、ARIA 正则、URL 黑名单、信任边界信封
**L4 ML 分类器 — TestSavantAI** — 22MB BERT-small ONNX 模型,本地运行,无网络。扫描每条用户消息和每个 Read/Glob/Grep/WebFetch 工具输出。可选 721MB DeBERTa-v3 ensemble。
**L4b 对话分类器** — Claude Haiku 审查完整对话形状(用户消息、工具调用、工具输出),而非仅文本。`LOG_ONLY: 0.40` 门控让大部分干净流量跳过付费调用。
**L5 Canary Token** — 会话开始时向系统提示注入随机 token。滚动缓冲区检测捕获 token 是否出现在 Claude 输出、工具参数、URL 或文件写入中。**确定性 BLOCK**——如果 token 泄露,攻击者说服 Claude 泄露了系统提示,会话立即结束。
**L6 组合决策器** — BLOCK 需要两个 ML 分类器在 >= WARN (0.75) 时达成一致,而非单个高置信度命中。这是 Stack Overflow 指令编写误报缓解策略。
---
## 五、SKILL.md 模板系统:把提示词当作软件工程
### 5.1 问题:文档漂移
SKILL.md 文件告诉 Claude 怎么使用 browse 命令。如果文档列出了不存在的 flag,或遗漏了新增的命令,agent 会出错。人工维护的文档永远与代码脱节。
### 5.2 解决方案:模板 + 自动生成
```
SKILL.md.tmpl (人类写的散文 + 占位符)
↓
gen-skill-docs.ts (读取源码元数据)
↓
SKILL.md (提交到 git,自动生成的章节)
```
占位符从源码在构建时填充:
| 占位符 | 来源 | 生成内容 |
|--------|------|---------|
| `{{COMMAND_REFERENCE}}` | `commands.ts` | 分类命令表 |
| `{{SNAPSHOT_FLAGS}}` | `snapshot.ts` | Flag 参考 + 示例 |
| `{{QA_METHODOLOGY}}` | `gen-skill-docs.ts` | 共享 QA 方法块 |
| `{{REVIEW_DASHBOARD}}` | `gen-skill-docs.ts` | 审查就绪仪表板 |
### 5.3 为什么提交到 git,而不是运行时生成?
三个原因:
1. **Claude 在 skill 加载时读取 SKILL.md** — 没有构建步骤,文件必须已存在且正确
2. **CI 可以验证新鲜度** — `gen:skill-docs --dry-run` + `git diff --exit-code` 在合并前捕获过时文档
3. **Git blame 有效** — 你能看到命令何时添加、在哪个 commit
### 5.4 三层测试体系
| 层级 | 内容 | 成本 | 速度 |
|------|------|------|------|
| 1 — 静态验证 | 解析 SKILL.md 中每个 `$B` 命令,对照注册表验证 | 免费 | <2s |
| 2 — E2E via `claude -p` | 生成真实 Claude 会话,运行每个 skill | ~$3.85 | ~20min |
| 3 — LLM-as-judge | Sonnet 评分文档清晰度/完整性/可执行性 | ~$0.15 | ~30s |
第 1 层每次 `bun test` 运行。2+3 层在 `EVALS=1` 后运行。理念:免费捕获 95% 的问题,LLM 仅用于判断性决策。
---
## 六、错误哲学:写给 AI 看的错误信息
### 6.1 错误是给 AI agents 的,不是给人类的
每个错误消息必须是**可操作的**:
| Playwright 原生错误 | gstack 重写后 |
|---------------------|--------------|
| "Element not found" | "Element not found or not interactable. Run `snapshot -i` to see available elements." |
| "Selector matched multiple elements" | "Selector matched multiple elements. Use <span class="mention-invalid">@refs</span> from `snapshot` instead." |
| Timeout | "Navigation timed out after 30s. The page may be slow or the URL may be wrong." |
Playwright 的原生错误通过 `wrapError()` 重写,剥离内部堆栈跟踪,添加指导。Agent 应该能够读取错误并知道下一步该做什么,**无需人类干预**。
### 6.2 崩溃恢复:不自我治愈
如果 Chromium 崩溃(`browser.on('disconnected')`),server **立即退出**。CLI 在下一次命令时检测到 dead server 并自动重启。
> 这比尝试重新连接到半死的浏览器进程更简单、更可靠。
这是一个设计选择:**失败要大声、要明确、要可恢复**——而不是静默地掩盖。
---
## 七、生产力飞轮:/scrape + /skillify
### 7.1 第一次:探索(~30 秒)
```
/scrape latest hacker news stories
→ AI 探索页面结构
→ 找到标题、链接、分数的选择器
→ 提取数据
```
### 7.2 /skillify:固化(一次性)
```
/skillify
→ 回溯对话,找到最后一次 /scrape 原型
→ 合成 Playwright 脚本 + 测试 + fixture
→ 运行测试
→ 询问后提交到 ~/.gstack/browser-skills/hn-front/...
```
### 7.3 第二次:执行(~200 毫秒)
```
/scrape hacker news front page
→ 检测到已有 codified skill
→ 直接执行 Playwright 脚本
→ 无需 AI 重新探索
```
**从 30 秒到 200 毫秒——150 倍加速**。这是复利效应:第一次用 AI 探索,后续用确定性脚本执行。
---
## 八、"烧干湖水"完整性原则
这是 gstack 最独特的哲学:
> **"AI 让完整实现的边际成本趋近于零,永远推荐完整方案。"**
| 任务类型 | 人工耗时 | Claude Code | 压缩比 |
|---------|---------|-------------|--------|
| 脚手架代码 | 2 天 | 15 分钟 | 100× |
| 写测试 | 1 天 | 15 分钟 | 50× |
| 功能实现 | 1 周 | 30 分钟 | 30× |
| Bug 修复 + 回归 | 4 小时 | 15 分钟 | 20× |
反模式:
- 错:选 B 吧,覆盖 90% 且代码更少 →(如果 A 只多 70 行,选 A)
- 错:跳过边界情况省时间 →(边界情况只需几分钟)
- 错:测试覆盖留到后续 PR →(测试是最便宜的"湖")
- 错:只报人工时间 "2 周" →(应该说 "人工 2 周 / CC 1 小时")
**核心洞察**:当 AI 让写代码便宜到近乎免费时,**"差不多就行"成了最昂贵的策略**。一个未处理的边界情况、一个遗漏的测试、一个简化的方案——这些在后续返工中的代价远超现在花几分钟让 AI 做完整。
---
## 九、日志架构:环形缓冲区的工程智慧
三个环形缓冲区(各 50,000 条,O(1) push):
```
浏览器事件 → CircularBuffer(内存中) → 异步刷盘到 .gstack/*.log
```
Console 消息、网络请求、dialog 事件各自有独立缓冲区。每秒刷新——server 只追加自上次刷新以来的新条目。
这意味着:
- HTTP 请求处理**永不阻塞磁盘 I/O**
- 日志在 server 崩溃后幸存(最多丢失 1 秒数据)
- 内存有界(50K × 3 缓冲区)
- 磁盘文件是**追加只写**,外部工具可读
`console`、`network`、`dialog` 命令从**内存缓冲区**读取,不是磁盘。磁盘文件用于事后调试。
---
## 十、E2E 测试基础设施:diff-based 的智能选择
### 10.1 Session Runner
E2E 测试生成 `claude -p` 作为完全独立的子进程——不是通过 Agent SDK(无法在 Claude Code 会话中嵌套):
1. 将提示写入临时文件(避免 shell 转义问题)
2. 生成 `sh -c 'cat prompt | claude -p --output-format stream-json --verbose'`
3. 从 stdout 流式接收 NDJSON 以获取实时进度
4. 与可配置超时竞争
5. 解析完整 NDJSON 对话为结构化结果
`parseNDJSON()` 函数是纯函数——无 I/O,无副作用——使其独立可测试。
### 10.2 Diff-Based 测试选择
```bash
bun run test:e2e # 仅运行与当前 diff 相关的测试
bun run test:e2e:all # 运行所有测试
```
每个测试在 `test/helpers/touchfiles.ts` 中声明其文件依赖。对全局 touchfiles 的变更触发所有测试。用 `EVALS_ALL=1` 或 `:all` 变体强制全部运行。
### 10.3 两层测试体系
| 层级 | 内容 | 触发条件 |
|------|------|---------|
| Gate | 安全护栏或确定性功能测试 | CI 每次提交 |
| Periodic | 质量基准、Opus 模型测试、非确定性 | 每周 cron 或手动 |
---
## 十一、给架构师的五个启示
### 11.1 "把提示词当作配置,而不是代码"
SKILL.md 是 markdown,不是 TypeScript。这意味着产品经理可以 review 它,设计师可以修改它,不需要编译就能生效。当 AI 的"行为"成为产品的一部分时,**行为定义应该用最高可读性的格式**。
### 11.2 "状态是性能最大的敌人"
浏览器自动化的瓶颈不是 Playwright,是**状态丢失**。每次关闭浏览器 = 丢失 cookies、登录态、tabs。gstack 用持久化 daemon 把这个成本从"每次命令 3 秒"降到"几乎为零"。
### 11.3 "错误信息是 API 的一部分"
当用户是 AI 而不是人类时,错误信息的设计目标完全变了。不是"让用户理解",是"让 AI 能自我修复"。每个错误都必须包含下一步行动指令。
### 11.4 "防御纵深,不是防御单点"
L1-L6 的安全架构不是过度工程。当 AI agent 有 Bash 工具、能读取网页、能写入文件时,一个 prompt injection 就能让它 `rm -rf /`。多层独立防御(内容过滤 + ML 分类 + canary token + 组合决策)确保任何单点突破都不致命。
### 11.5 "从探索到执行的分层抽象"
`/scrape` 是探索层(AI 理解页面结构),`/skillify` 是固化层(生成确定性脚本),后续调用是执行层(直接运行脚本)。这是**分层抽象的经典模式**——高层灵活、低层高效。
---
## 结语:为什么这个项目重要
gstack 不是"又一个 AI 工具"。它是**AI 时代软件开发工作流的原型**。
它证明了三件事:
1. **一个人可以管理 15 个 AI agent 的并行开发**(Conductor 模式)
2. **提示词可以软件工程化管理**(SKILL.md 模板系统 + 三层测试)
3. **AI 的边际成本趋近于零时,"完整实现"比"精简方案"更便宜**(烧干湖水原则)
Garry Tan 的 400 倍生产力不是因为他用了更好的模型,而是因为他**把 AI 当作一个工程团队来管理,而不是一个更快的高级程序员**。
这就是 gstack 的架构真相。
---
**参考来源:**
- gstack GitHub: https://github.com/garrytan/gstack
- ARCHITECTURE.md — gstack 核心架构文档
- BROWSER.md — 浏览器系统完整参考
- docs/skills.md — 技能深度解析
- docs/ON_THE_LOC_CONTROVERSY.md — 生产力度量方法论
- CLAUDE.md — 开发文档
- AGENTS.md — 可用技能清单
#gstack #GarryTan #AI编程 #架构解析 #ClaudeCode #浏览器自动化 #PromptEngineering #软件工程 #Tokenmaxxing #HeavyGrok
#架构解析 #gstack #GarryTan #AI编程 #ClaudeCode #浏览器自动化 #PromptEngineering #软件工程 #Tokenmaxxing #HeavyGrok
登录后可参与表态
讨论回复
0 条回复还没有人回复,快来发表你的看法吧!
推荐
推荐
智谱 GLM-5 已上线
我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。
领取 2000万 Tokens
通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力