在写第一行代码之前:一次用 AI「grill-me」出完整项目架构的实战复盘

文章目录

你以为「从零搭建一个新项目」会从 git init 和敲下第一行代码开始。但在这场长达一小时四十多分钟的直播里,真正动手写业务代码的时间是零。所有时间都花在了「想清楚到底要做什么」上------而这恰恰是大多数人最容易跳过、也最容易翻车的部分。

这是一篇关于如何用 AI 把一个模糊的想法,逼问成一套清晰可落地的项目架构的复盘。主角是 TypeScript 教育者 Matt Pocock,他在一次罕见的直播中,借助 Claude Code 等编码 Agent,把一个临时起意的点子,一路推进到「研究文档 + 领域模型 + 统一语言 + 基础架构」全部就位的状态。整个过程没有写一行功能代码,但项目的骨架已经清晰得可以直接开工。

对开发者来说,这场直播的价值不在于「他做了一个什么产品」,而在于他展示了一套可复制的 0-1 规划工作流:如何与 AI 协作做研究、如何管理上下文、如何用领域驱动设计(DDD)锻造语言、以及如何在抽象阶段做出恰到好处的技术决策。

一、重新理解「从零开始」:绿地项目的本质是研究

大多数人对「写新项目」的想象是:打开编辑器,搭好脚手架,开始写代码。但真正有经验的工程师会告诉你,在敲下第一行代码之前,项目的成败其实已经决定了一大半。

这场直播给出的核心论断是:在真正进入代码之前,绿地(greenfield)项目本质上是一项研究任务。

在你把想法落成 PRD、把决策固化成代码、看着设想被「具象化」之前,你所做的一切都只是研究。这听起来像是在拖延,但事实恰恰相反------它是在用最廉价的方式(对话和文档)消除最昂贵的风险(方向错误、推倒重来)。

这个阶段的产物不是代码,而是三样东西:

  • 研究文档:把零散的调研结论沉淀下来
  • 领域模型与统一语言:确定每一个核心概念叫什么、是什么
  • 基础架构决策:搞清楚有哪些可部署单元、它们如何协作

下面我们顺着这条主线,看 Matt 是如何一步步把空气般的想法,凝结成可施工的蓝图的。

二、核心武器:Grill Me ------ 让 AI 提问,而不是回答

整场直播反复出现一个关键词:grilling session(拷问式会话) 。它背后是一个名叫 Grill Me 的技能(skill)。

绝大多数人用 AI 的方式是「我问,它答」。而 Grill Me 把这个关系彻底反转:你给它一个粗糙的想法,它反过来连珠炮般地拷问你,逼你在每一个岔路口做出明确选择。

这套做法的精妙之处在于:

它其实并不是在替我回答问题。它在不断抛出问题,逼着我自己把那些原本含混的想法说清楚。这一点至关重要。

换句话说,AI 在这里扮演的不是「代笔」,而是一个永不疲倦、思维缜密的结对思考者。它会在你还没意识到的地方制造分叉:

  • 你的核心用户到底是谁?是个人开发者、工程经理,还是公司平台层的决策者?
  • 这三类人看似兼容,实则会导出完全不同的 UX:个人用户要的是单次会话的时间线与深挖;经理要的是跨人对比的看板;高管只关心聚合指标,根本不在乎个体。

这种「看似相近、实则水火不容」的辨析,正是一个人在兴奋期最容易忽略、却会在三个月后让产品撕裂的地方。

配套还有一个轻量技能 Zoom Out,作用类似「请用最直白的话解释给我听」。当对话里冒出 OIDCIDP 这类把人绕晕的术语时,一句 Zoom Out 就能把它拉回到能听懂的高度------既不打断节奏,又不会不懂装懂地往下走。

这两个技能合在一起,构成了一种健康的人机分工:AI 负责把问题铺开、把术语讲透,人负责做价值判断和取舍。

三、第一阶段:把「我想做点东西」收敛成一个真问题

直播的起点其实相当随意------Matt 只是想找一个能长期当作内容素材、又对自己日常工作真正有用的「练手项目」。他给自己定了几条约束:

  • 要在日常工作中真正用得上
  • 要同时包含前端和后端
  • 要有足够的复杂度
  • 最好对观众也有价值,比如和 AI 编码相关

在一堆候选里(待办应用、GitHub 替代品、书签管理器......),一个想法逐渐浮出水面并最终胜出:一个面向编码 Agent 的可观测性平台

这个方向之所以站得住脚,是因为它直击一个正在快速变成普遍痛点的真实场景:当一个组织开始大规模使用编码 Agent 时,团队会迫切想知道------

  • 每个人/每次会话烧了多少 token?成本花在哪儿了?
  • 会话是否成功、是否高效?
  • 大家在用哪些模型、占用了多少上下文窗口?
  • 哪些 prompt、工具、MCP 真正物有所值,哪些地方在「漏钱」?

用一句话概括:应用层的 AI 可观测性已经有人在做,但「你自己的编码 Agent」这一层的可观测性,是缺失的。

值得注意的是产品哲学上的两条早期定调,它们后来深刻影响了所有技术决策:

  1. 数据归属于用户。 不做中心化托管服务,不碰用户数据,支持本地/本地化(on-premises)部署,让数据永不离开组织自己的服务器。
  2. 开源、可插拔。 目标是一个到处都能用的开源工具,存储机制可替换,而不是一门要做大的生意。

四、第二阶段:并行研究五大编码 Agent 的数据采集机制

方向定下后,第一个真正的技术问题浮现:会话数据到底怎么进入系统? 这决定了客户端的整体形态,是整个系统的「技术脊柱」。

这里 Matt 做了一个很多人会忽略的动作:先研究,再决策。他没有凭直觉拍板,而是让 AI 并行启动了多个子 Agent,分别去调研当下主流编码 Agent 的采集方式:

  • Claude Code
  • OpenAI Codex CLI
  • Pi
  • Open Code
  • GitHub Copilot CLI

研究结论相当有价值,也直接推翻了「找一个统一标准就万事大吉」的天真设想:

发现 含义
每个 Agent 都提供「事件 hook + 追加写入的 JSONL 文件」两种面 采集有两条可用通道
各家 schema 全不相同,且都在持续演进 不存在「直接上 OpenTelemetry」这种银弹
Claude Code 的 hook 负载里不含消息正文 光靠 hook 不够,仍需读取 JSONL
Copilot 的 hook 负载很「薄」,Codex 的 hook 受 flag 限制且不支持 Windows 能力参差不齐,无法一刀切

最终结论非常务实:按 Agent 分别适配,无法回避。 每个适配器使用该 Agent 最合适的通道------能拿到实时事件就用 hook,拿不全就用 JSONL 兜底------再统一归一化(normalize)成内部的事件 schema。本地只跑一个进程,根据目标 Agent 加载对应的适配器。

这里有一个对所有做集成的人都适用的清醒认知:

不同 Agent 必然吐出不同的 schema 和形态,而且会变。比如 Pi 的输出结构最近一个补丁版本就变了。所以无论怎么做,我们都得长期为这些系统「擦屁股」。

承认这种不可控性,并把它设计进系统(而不是幻想一个永远稳定的标准),本身就是一种成熟的工程态度。

五、上下文管理:把对话压缩成研究文档与 ADR

研究做完,一个很现实的问题来了:这段对话消耗了四万多 token,积累了大量有价值的判断,怎么把它保存下来、带到后续工作里?

Matt 的做法揭示了一套极其实用的**上下文工程(context engineering)**思路:

  1. 及时落盘。 把当前上下文窗口里的研究结论,压缩、总结成一份 Markdown 研究文档,持久化到代码仓库里。于是他顺势 git init 起了一个新仓库,写下第一份 research/coding-agent-ingestion 文档。
  2. 主动「裁剪」上下文。 当对话里掺进了无关的支线(比如花了好久给项目起名),他会用「回退/resume 到某个更早的回合」的方式,直接把那段上下文剪掉------相当于让对话「回到过去」,只保留研究阶段结束时那个干净的状态。
  3. 用「一个 Claude 回答另一个 Claude」。 当新会话需要旧会话里的上下文、而它们又不在同一目录时,他甚至会回到旧会话,把新会话抛来的问题贴进去,让旧 Agent「根据我们之前的结论来回答」,再把答案搬过去。

关于「长期记忆」,他给出了一个值得深思的反主流观点:

我认为给 Agent 做长期记忆是个糟糕的主意。你想要的是某种高度可观测、足够具体、能立刻编辑、又能塞进上下文窗口的东西。

他给出的替代方案是 ADR(架构决策记录,Architectural Decision Records) 加上一套精简版的 DDD。决策不是藏在模型不透明的「记忆」里,而是白纸黑字写在仓库的文档中------可读、可改、可追溯。这对一个「可观测性平台」来说,几乎是一种自洽的优雅。

六、第三阶段:用 DDD 锻造统一语言(Ubiquitous Language)

如果说前面是「想清楚做什么」,这一阶段就是把直播推向高潮的部分:领域建模 。Matt 的判断是,DDD 和 AI 编码是绝佳搭档,而其中最关键的一环,是建立统一语言(ubiquitous language)

他的逻辑链条非常清晰:

语言------也就是我们谈论这个应用的方式------极其重要。如果 session 这个词是模糊的,那整个数据模型就是模糊的。

人和 AI 必须在「怎么称呼每一个东西」上完全同步,否则后续生成的代码必然走样。于是他又开了一轮拷问式会话,这次用的是 Grill Me 的进阶版 domain model 技能,逐个敲定核心概念。

从 Session 到 DAG:一个会话到底是什么

AI 抛出的第一个术语是 session(会话):一次编码 Agent 从启动到退出的连续运行,绑定到一个开发者、一个工作目录、一个 Agent 版本。听起来天经地义,但紧接着的边界拷问才是真正值钱的地方:

  • resume(恢复会话)算同一个 session,还是一个指向父节点的新 session?
  • 并发的子任务会派生子 Agent、各自写自己的 JSONL,那么每个子 Agent 是父会话的一部分,还是一个子会话?
  • 当用户在某个决策点「分叉」(尤其是 Pi 这类支持 rewind/branch 的 Agent),会话就不再是一条直线,而是一棵树。这要怎么在数据模型和 UI 里表达?

AI 用两个具体场景把这种模糊逼到了墙角:

场景 A(探索者): Alice 用 Pi 重构一个模块,十个回合后到了决策点。她分叉去试「中间件方案」,试了八个回合不满意,退回决策点,改试「就地重构」,十二个回合后发版。那条被放弃的中间件分支烧了真金白银的 token、也产生了真实的文件改动。那么------这算三个 session 还是一个带有向无环图(DAG)的 session?她这周的会话计数应该 +3 还是 +1?

正是这种「用具体剧情road-test语言」的手法,体现了 DDD 的精髓:当你能用一套语言轻松描述各种刁钻场景时,这套语言通常也更容易落成代码。

经过反复推敲,领域模型逐渐定型:
#mermaid-svg-ndSZUFLslsVnPC2I{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ndSZUFLslsVnPC2I .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ndSZUFLslsVnPC2I .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ndSZUFLslsVnPC2I .error-icon{fill:#552222;}#mermaid-svg-ndSZUFLslsVnPC2I .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ndSZUFLslsVnPC2I .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ndSZUFLslsVnPC2I .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ndSZUFLslsVnPC2I .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ndSZUFLslsVnPC2I .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ndSZUFLslsVnPC2I .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ndSZUFLslsVnPC2I .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ndSZUFLslsVnPC2I .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ndSZUFLslsVnPC2I .marker.cross{stroke:#333333;}#mermaid-svg-ndSZUFLslsVnPC2I svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ndSZUFLslsVnPC2I p{margin:0;}#mermaid-svg-ndSZUFLslsVnPC2I .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ndSZUFLslsVnPC2I .cluster-label text{fill:#333;}#mermaid-svg-ndSZUFLslsVnPC2I .cluster-label span{color:#333;}#mermaid-svg-ndSZUFLslsVnPC2I .cluster-label span p{background-color:transparent;}#mermaid-svg-ndSZUFLslsVnPC2I .label text,#mermaid-svg-ndSZUFLslsVnPC2I span{fill:#333;color:#333;}#mermaid-svg-ndSZUFLslsVnPC2I .node rect,#mermaid-svg-ndSZUFLslsVnPC2I .node circle,#mermaid-svg-ndSZUFLslsVnPC2I .node ellipse,#mermaid-svg-ndSZUFLslsVnPC2I .node polygon,#mermaid-svg-ndSZUFLslsVnPC2I .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ndSZUFLslsVnPC2I .rough-node .label text,#mermaid-svg-ndSZUFLslsVnPC2I .node .label text,#mermaid-svg-ndSZUFLslsVnPC2I .image-shape .label,#mermaid-svg-ndSZUFLslsVnPC2I .icon-shape .label{text-anchor:middle;}#mermaid-svg-ndSZUFLslsVnPC2I .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ndSZUFLslsVnPC2I .rough-node .label,#mermaid-svg-ndSZUFLslsVnPC2I .node .label,#mermaid-svg-ndSZUFLslsVnPC2I .image-shape .label,#mermaid-svg-ndSZUFLslsVnPC2I .icon-shape .label{text-align:center;}#mermaid-svg-ndSZUFLslsVnPC2I .node.clickable{cursor:pointer;}#mermaid-svg-ndSZUFLslsVnPC2I .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ndSZUFLslsVnPC2I .arrowheadPath{fill:#333333;}#mermaid-svg-ndSZUFLslsVnPC2I .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ndSZUFLslsVnPC2I .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ndSZUFLslsVnPC2I .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ndSZUFLslsVnPC2I .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ndSZUFLslsVnPC2I .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ndSZUFLslsVnPC2I .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ndSZUFLslsVnPC2I .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ndSZUFLslsVnPC2I .cluster text{fill:#333;}#mermaid-svg-ndSZUFLslsVnPC2I .cluster span{color:#333;}#mermaid-svg-ndSZUFLslsVnPC2I div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ndSZUFLslsVnPC2I .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ndSZUFLslsVnPC2I rect.text{fill:none;stroke-width:0;}#mermaid-svg-ndSZUFLslsVnPC2I .icon-shape,#mermaid-svg-ndSZUFLslsVnPC2I .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ndSZUFLslsVnPC2I .icon-shape p,#mermaid-svg-ndSZUFLslsVnPC2I .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ndSZUFLslsVnPC2I .icon-shape .label rect,#mermaid-svg-ndSZUFLslsVnPC2I .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ndSZUFLslsVnPC2I .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ndSZUFLslsVnPC2I .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ndSZUFLslsVnPC2I :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} parent_session_id
Session 会话
Turn 回合
Turn 回合
Turn 分支回合
Model Request 模型请求
Model Request 模型请求
子 Session sub-agent

  • Session(会话):逻辑上的一次运行,一行记录,内部包含一个由 Turn 组成的有向无环图(DAG)。
  • Turn(回合) :DAG 上的一个节点,带有 parent_turn_id。一个回合 = 一条用户消息 + 完整的助手响应。大多数 Agent 产出的是「退化的 DAG」(一条直线),只有 Pi 这类会产生真正的分支。
  • Model Request(模型请求):一个回合内,Agent 向模型方发起的一次 HTTP 调用。
  • 子 Agent :作为带 parent_session_id 的子会话存在。

这里还藏着一个容易踩的术语坑,也被 AI 精准点出:

你说 turn 对应一次 API 调用,这在标准 Agent 术语里是不对的。在大多数 Agent 自己的文档里,一个 turn 指「一条用户消息加上完整的助手响应」。

于是「一次 HTTP 调用」被单独命名为 model request,与 turn 分开。一个回合里 Agent 可能连打 22 次工具调用、每次都是单独计费的 model request------这种区分,正是「这次会话花了 14 美元,钱到底花哪了」这种核心问题能被回答的前提。

至于 resume 和 compaction(上下文压缩)怎么建模,Matt 做了一个非常克制的决定:先搁置,等看到基础版本跑起来、有了真实数据再说。 在纯抽象阶段硬抠这些边界,投入产出比极低。

七、关键架构决策:在抽象与务实之间走钢丝

伴随语言一起被敲定的,是一系列架构决策。难能可贵的是,每一个决策都伴随着「先上简单版、验证想法、留好升级路径」的克制。

采集端:从 sidecar 到 listener

跑在开发者机器上、负责采集数据的那个组件,经历了一场漫长的命名拉锯。它先被叫做 sidecar(旁车进程)------一个不错的比喻,指它运行在编码 Agent 旁边而非内部。

关于它的形态,有一个关键的认知翻转:

  • 最初设想是一个常驻后台守护进程(daemon) ,持续监视所有配置的 Agent。但这有个致命问题:人们会忘记启动它。
  • 研究表明纯 hook 又不够(拿不到完整消息正文)。
  • 最终方案:由 hook 来启动一个「随会话存活」的旁路子进程session start hook 触发采集,只在单次会话的生命周期内存在,会话结束即退出。

这个模型的妙处在于:

从用户视角看,这是「无守护进程」的。他们只需一次性配置好 hook/扩展,安装即配置------hook 配置本身就是安装,「忘记启动守护进程」的问题就此消解。

至于名字,从 sidecarcollectorwatcherlistenertap、甚至一堆搞笑的 peeper/stalker,争论了很久。最后 Matt 一锤定音:

就先叫 listener(监听器)吧,够用了,以后能改。我们只要做到「足够好」就行,别整天为这点语言纠结。

这句话本身就是一条朴素而重要的工程纪律:命名很重要,但不要陷入无止境的「刷漆棚效应」(bike-shedding)。

认证:V1 够用就好,V2 再上 OIDC

关于「会话如何绑定到开发者身份、采集端如何向后端认证」,AI 一上来就抛出了企业级的 OIDC + IDP(身份提供方)+ OAuth 设备流方案。Matt 用 Zoom Out 把这些术语弄明白后,做了一个典型的产品判断:

这套太重了。我想知道有没有一个「糙一点」的认证版本,能让 V1 早点跑起来,先验证想法。我不想为了走完整套认证协议而把验证想法的时机拖到很晚。

于是定下分层方案:

  • V1 :管理员在自托管后端的管理页上「添加用户」,生成一个一次性 token 字符串,通过 Slack 之类交给开发者;开发者运行 npx <listener> login 粘贴进去,之后每个请求都带上这个 bearer token。身份可信(每个 token 绑定一条用户记录),撤销即「踢人」,且升级路径干净。
  • V2:再接入 OIDC/OAuth 设备流,对接组织的 IDP。

这背后是一条经典软件工程原则:尽早验证想法。在抽象阶段,你能把一切都规格化,但你真正需要的是看到它跑起来。

后端:单一二进制 + 独立的 Postgres

后端服务(最终定名为 server)的形态,在「单一二进制 / Docker Compose / Kubernetes」之间做选择。Matt 排除了 Kubernetes,倾向于**默认单一二进制(batteries-included)**的混合方案,但在存储上做了清晰切分:

我更想要 Postgres 而不是 SQLite 后端。数据库需要比应用的变更活得更久,而且我希望它和二进制运行在不同的可部署单元上。

语言选型上,他在 Bun(可打包单一可执行文件的 TypeScript)、Go、Rust 之间摇摆------甚至坦言对从没写过的 Rust 颇感兴趣,因为「需要给不熟悉的语言仓库做贡献,本身就是真实场景下的痛点」。前端则确定用熟悉的 React(他也明确表示 Svelte 同样成熟,无伤大雅)。

实时观战:用轮询而非推送

对于「经理实时围观某个开发者会话」这种 live spectate 需求,AI 又开始往「100 毫秒内把事件扇出到浏览器」的高复杂度方案上冲。Matt 再次踩了刹车:

用更简单的办法。我觉得用轮询就行,没必要那么实时。每 5 秒轮询一次,或者用户切回标签页时刷新一下,基本就够用了。

这种「在抽象阶段不为想象中的性能需求过度设计」的克制,贯穿了整场决策。

最终,整个系统的形态清晰浮现:
#mermaid-svg-nhUp36OLINeLN9gj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nhUp36OLINeLN9gj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nhUp36OLINeLN9gj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nhUp36OLINeLN9gj .error-icon{fill:#552222;}#mermaid-svg-nhUp36OLINeLN9gj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nhUp36OLINeLN9gj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nhUp36OLINeLN9gj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nhUp36OLINeLN9gj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nhUp36OLINeLN9gj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nhUp36OLINeLN9gj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nhUp36OLINeLN9gj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nhUp36OLINeLN9gj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nhUp36OLINeLN9gj .marker.cross{stroke:#333333;}#mermaid-svg-nhUp36OLINeLN9gj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nhUp36OLINeLN9gj p{margin:0;}#mermaid-svg-nhUp36OLINeLN9gj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nhUp36OLINeLN9gj .cluster-label text{fill:#333;}#mermaid-svg-nhUp36OLINeLN9gj .cluster-label span{color:#333;}#mermaid-svg-nhUp36OLINeLN9gj .cluster-label span p{background-color:transparent;}#mermaid-svg-nhUp36OLINeLN9gj .label text,#mermaid-svg-nhUp36OLINeLN9gj span{fill:#333;color:#333;}#mermaid-svg-nhUp36OLINeLN9gj .node rect,#mermaid-svg-nhUp36OLINeLN9gj .node circle,#mermaid-svg-nhUp36OLINeLN9gj .node ellipse,#mermaid-svg-nhUp36OLINeLN9gj .node polygon,#mermaid-svg-nhUp36OLINeLN9gj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nhUp36OLINeLN9gj .rough-node .label text,#mermaid-svg-nhUp36OLINeLN9gj .node .label text,#mermaid-svg-nhUp36OLINeLN9gj .image-shape .label,#mermaid-svg-nhUp36OLINeLN9gj .icon-shape .label{text-anchor:middle;}#mermaid-svg-nhUp36OLINeLN9gj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nhUp36OLINeLN9gj .rough-node .label,#mermaid-svg-nhUp36OLINeLN9gj .node .label,#mermaid-svg-nhUp36OLINeLN9gj .image-shape .label,#mermaid-svg-nhUp36OLINeLN9gj .icon-shape .label{text-align:center;}#mermaid-svg-nhUp36OLINeLN9gj .node.clickable{cursor:pointer;}#mermaid-svg-nhUp36OLINeLN9gj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nhUp36OLINeLN9gj .arrowheadPath{fill:#333333;}#mermaid-svg-nhUp36OLINeLN9gj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nhUp36OLINeLN9gj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nhUp36OLINeLN9gj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nhUp36OLINeLN9gj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nhUp36OLINeLN9gj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nhUp36OLINeLN9gj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nhUp36OLINeLN9gj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nhUp36OLINeLN9gj .cluster text{fill:#333;}#mermaid-svg-nhUp36OLINeLN9gj .cluster span{color:#333;}#mermaid-svg-nhUp36OLINeLN9gj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nhUp36OLINeLN9gj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nhUp36OLINeLN9gj rect.text{fill:none;stroke-width:0;}#mermaid-svg-nhUp36OLINeLN9gj .icon-shape,#mermaid-svg-nhUp36OLINeLN9gj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nhUp36OLINeLN9gj .icon-shape p,#mermaid-svg-nhUp36OLINeLN9gj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nhUp36OLINeLN9gj .icon-shape .label rect,#mermaid-svg-nhUp36OLINeLN9gj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nhUp36OLINeLN9gj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nhUp36OLINeLN9gj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nhUp36OLINeLN9gj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 归一化事件
每 5s 轮询
编码 Agent

Claude Code / Codex / Pi ...
Listener

随会话启动的旁路进程
Server

自托管后端
Postgres
Dashboard 仪表盘
Admin 管理面
经理 / DRI 浏览器

用一句话串起来:这是一个自托管、本地化的编码 Agent 可观测性平台。开发者在编码 Agent 旁运行一个 listener,listener 把会话信息上报给 server;server 接收事件、存入 Postgres、提供仪表盘,并托管管理面,一个组织一套。

八、命名的艺术与陷阱:从 Yardstick 到 Slop Watch

值得单独一提的是项目命名这条支线,因为它既好笑又真实。在确定架构之前,Matt 花了大量时间给项目找一个「临时占位名」,结果 AI 和直播间观众贡献了一长串候选:Agent Scope、Telescope、Panopticon、Palantir(已被占用)、Yardstick、Crow's Nest......

他一度准备用偏「度量感」的 Yardstick,直到一位观众抛出 Slop Watch(姑且译作「糊弄监察」)------一个既精准点题(监控 AI 产出的「slop/糊弄活」)、又自带幽默感的名字,瞬间胜出。

这条支线的启示有二:

  1. 好名字能瞬间「结晶」一个想法。 一个贴切的名字会让整个项目突然变得真实、可感。
  2. 命名极易上瘾,也极易浪费 token。 Matt 自己也承认在起名上「烧了不少 token」,事后觉得本可以更高效。这再次印证了那条纪律:在「足够好」处及时收手。

九、提炼:一套可复制的 AI 协作工作流

抛开 Slop Watch 这个具体产物,这场直播真正可迁移的,是下面这套方法论和心法。

工作流四步走

  1. 确定主题与读者(用户)。 先用拷问式会话把「核心用户是谁、他要做什么决策」钉死,因为不同用户会导出完全不同的产品。
  2. 并行研究。 让多个子 Agent 并行调研技术选项,把结论沉淀成研究文档,而不是凭直觉拍板。
  3. 领域建模。 用 DDD 锻造统一语言,把每个核心概念命名、定义、用具体场景压力测试,沉淀到 context.md 这样的术语表里。
  4. 固化与压缩。 用 ADR 和研究文档持久化决策,主动裁剪上下文,让对话始终保持干净、可延续。

几条反直觉的心法

  • 绿地项目在见到代码之前,本质是研究。 别急着搭脚手架。
  • 让 AI 提问,而非替你回答。 它最大的价值是逼你把模糊想法说清楚,以及检验你和它是否「对齐」。
  • 尽早验证想法,别过度规格化。 V1 用够用的糙方案,留好升级路径;很多边界问题等有了真实数据再定。
  • 别过度 review 计划。 在规格和方案上反复打磨却迟迟不动手,是 Web 开发的经典误区------你需要尽快走到代码。
  • 语言是把程序带入生命的方式。 一旦每个术语都被命名、定义清楚,后续工作几乎就「上了轨道」。
  • 人 + AI 永远胜过单独的 AI。 帮 AI 一把,它就能做得更好;无论模型多强,这一点都不会变。
  • 别对 token 太焦虑,尤其是输入 token。 输入 token 极其廉价,真正值得在意的是方向对不对。
  • 接受抓不全边界 case。 在规划阶段穷举所有边界几乎不可能,要把「事后 QA」内建进流程,而不是幻想一次想全。
  • 用「改进代码库架构」这类技能定期体检。 复杂的应用难以修改,简单的应用易于修改;定期重构不仅让代码更好改,也让 AI 的反馈回路更顺,从而少产出垃圾代码。

结语:工程的重心正在前移

这场直播最反直觉、也最值得回味的一点是:近两个小时里没有写一行业务代码,但项目其实已经「基本成型」。

当编码本身越来越多地交给 AI,工程师的价值重心正在显著前移------从「把代码写对」转向「把要做的事想清楚、说清楚、对齐清楚」。研究、领域建模、统一语言、架构决策,这些过去常被当作「正式开工前的热身」的环节,如今才是真正决定成败、也最难被 AI 替代的部分。

用直播尾声那句话作结再贴切不过:

这是不是工程?我觉得是。大量的拷问、大量地搞清楚你到底要做什么、大量的对齐、把所有零件都摆到位。一旦摆齐了,剩下的------放手让它跑就行。

对每一个正在与 AI 协作的开发者来说,这或许才是当下最该练的那块肌肉。