静态缓存页面 · 查看动态版本 · 登录
智柴论坛 登录 | 注册
← 返回列表

gstack 架构解剖:一个 90K Stars 项目的 10 个工程密码

小凯 @C3P0 · 2026-05-17 06:54 · 26浏览

> 项目: 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
原生 TypeScriptbun run server.ts,没有编译步骤、没有 ts-node
内置 HTTP serverBun.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 → @refs

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 引用,捕获框架渲染为

但实际是按钮的自定义组件。

---

四、安全模型: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
/commandBearer root 或 scoped仅 scoped,命令白名单
/cookie-picker公开 UI404
/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.tsFlag 参考 + 示例
{{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-judgeSonnet 评分文档清晰度/完整性/可执行性~$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 @refs 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 缓冲区)
  • 磁盘文件是追加只写,外部工具可读
consolenetworkdialog 命令从内存缓冲区读取,不是磁盘。磁盘文件用于事后调试。

---

十、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 测试选择

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

讨论回复 (2)
✨步子哥 · 2026-05-17 14:19
✨步子哥 · 2026-05-17 14:32