# 博客文章顾问审阅资料包 — Batch-03 > **批次编号**:`Batch-03` > **包含文章数**:11 篇 > **本批次总字符量**:171,890 字(严格限制 < 200,000 字) > **模式**:BILINGUAL > **生成时间**:2026-10-27 03:02:03 # 外部顾问审阅前置须知与工程背景规范 > **本文档目标**:本批次包含 `blog.hoelee.com` 的待审阅技术文章全文。请外部顾问在审阅正文与技术主张时,以此规范为最高评估准则。 ## 1. 站点定位与双重业务目的 - **受众 A(技术招聘官 / 资深技术主管)**:寻找具备 Senior 全栈 + DevOps + AI 智能体闭环能力的架构工程师。文章定位为**“不可伪造的工程实操交付证据(Proof of Expertise)”**,要求代码真实、日志可查、有架构权衡与技术深度。 - **受众 B(中小企业主 SME / 商业客户)**:寻找能提供系统研发、私有化部署、24/7 AI 获客系统、高可用基础设施的技术顾问。文章需清晰阐述业务痛点、商业 ROI、上线效率与交付保障。 ## 2. 核心人设铁律与工程硬约束(必须严格遵守) 1. **彻底杜绝业余爱好者/玩具口吻**: - ❌ **绝对禁用词**:`homelab`, `home lab`, `家用`, `小主机`, `玩具`, `折腾`。 - ✅ **标准替代词**:`the infrastructure I manage`(我管理的私有基础设施)、`self-hosted production node`(自托管生产节点)、`on-premise host`(私有化服务器)。 2. **严禁捏造虚假指标与性能数字**: - 所有 benchmark 数据必须有明确的测试基准与单设备限制。 - 严格区分“耗时减少比例”与“吞吐加速倍数”(例如耗时从 352s 降至 218s 是耗时减少 38.0%,吞吐提升 1.61×,严禁偷换概念混淆)。 - 时间戳精度与物理帧对齐:视频帧率上限(如 30 fps 单帧 33.3ms)不可妄称“微秒级精确”。 3. **去模板化叙事与真实物理锚点**: - 拒绝千篇一律的 AI 模板味。文章采用三大叙事架构:① 故障排查谜案(Symptom → False Lead → Kernel Evidence → Fix);② 商业交付复盘(Client Bottleneck → Trade-offs → State Machine → ROI);③ 架构演进对比(V1 Naive → Concurrency Crash → V2 Redesign)。 - 每篇文章必须包含至少一个物理真实世界锚点(主机真实硬件规格、生产维护窗口、真实业务场景)。 4. **代码与配置展示规范**: - 超过 20 行的配置或日志采用 HTML `
` 原生折叠,`` 必须精准标注文档名与行数。 ## 3. 顾问重点开刀维度 - [ ] **事实与数字可核性**:技术判断与性能数据是否自洽?有无过度夸大? - [ ] **人设与语调纯洁性**:有无遗漏的 hobbyist/homelab 口吻? - [ ] **双轨转化引导有效性**:是否有清晰的业务价值提炼与行动召唤(CTA)? - [ ] **代码与生产安全性**:有无未脱敏的凭据、绝对内部私密路径或不安全的权限配置? --- ## 本批次文章目录索引 1. [case-studies] **代码只让一个 AI 改:我是如何避开多 Agent 协同踩坑的「单写多审」架构** (`the-single-writer-rule-for-coding-agents`) — ~20,184 字符 2. [case-studies] **什么时候该让 AI 操控物理鼠标(以及什么时候绝对不要用)** (`when-to-give-your-agent-physical-clicks`) — ~10,447 字符 3. [case-studies] **为什么我要做视频精读知识库:把收藏夹吃灰的短视频变成可检索笔记** (`why-i-built-a-video-knowledge-base`) — ~8,631 字符 4. [case-studies] **为什么你的无头浏览器不是万能爬虫** (`why-your-headless-browser-cant-scrape-everything`) — ~11,856 字符 5. [devops] **一个字符让我的 authentik CSS 静默失效** (`authentik-css-greater-than-bug`) — ~14,691 字符 6. [devops] **验证完全正确的 Forward-Auth 网关——其实并未上线** (`authentik-forward-auth-gate-wasnt-live`) — ~25,254 字符 7. [devops] **Authentik 从 2025.8 升级到 2026.8:那些没人提醒你会坏掉的部分** (`authentik-major-upgrade-gotchas`) — ~17,725 字符 8. [devops] **不用界面管理 CyberPanel:逆向一个没有文档的 v2 API** (`automating-cyberpanel-without-the-ui`) — ~13,718 字符 9. [devops] **Docker 报告退出码 0,内核却早已执行 OOM-Kill** (`docker-reported-exit-0-the-oom-killer-disagreed`) — ~15,666 字符 10. [devops] **每个播放器都在同一个视频上卡顿,而文件本身是无辜的** (`every-player-stuttered-the-file-was-fine`) — ~11,805 字符 11. [devops] **加固 Tor 洋葱服务:真正重要的是什么** (`hardening-a-tor-onion-service`) — ~21,913 字符 --- ## 文章 01 / 11:代码只让一个 AI 改:我是如何避开多 Agent 协同踩坑的「单写多审」架构 - **Slug**: `the-single-writer-rule-for-coding-agents` - **英文标题**: The Single-Writer Rule: Orchestrating Coding Agents Without Multi-Agent Chaos - **分类**: `case-studies` | **发布日期**: 2026-08-26 - **线上地址**: https://blog.hoelee.com/posts/the-single-writer-rule-for-coding-agents/ (EN) | https://blog.hoelee.com/posts/zh/the-single-writer-rule-for-coding-agents/ (ZH) - **本篇审核状态**: ⏳ 待审核 (Pending) ### ✍️ 顾问审阅意见区 (Review Feedback) ```text 【审阅结论】:[ ] 通过 [ ] 需微调 [ ] 需重构 【事实与数字核对】: 【人设与用词卫生】: 【CRO与转化建议】: ``` ### 🇨🇳 中文版原文 (Chinese Markdown Twin) ````markdown --- title: "代码只让一个 AI 改:我是如何避开多 Agent 协同踩坑的「单写多审」架构" description: "为什么让多个 AI Agent 同时写代码必然导致分支冲突与隐式失真?深度拆解「单写进程 + 独立素材子进程 + 对抗式代码审查」实战架构。" pubDate: 2026-08-26 updatedDate: 2026-10-25 category: case-studies blueprint: ai-agent-automation blueprintLayer: L2-llm-gateway-agents tags: ["ai", "agents", "architecture", "devops"] ogImage: "/og/zh-the-single-writer-rule-for-coding-agents.jpg" banner: "/banners/the-single-writer-rule-for-coding-agents.jpg" draft: false --- ## 为什么这至关重要 当开发团队刚开始尝试 AI 编程代理(Coding Agents)时,直觉上的下一步往往是“开一整队”:如果一个 Agent 在 10 分钟内能写好一个功能,那么同时启动 4 个 Agent 并行协作,岂不是 3 分钟就能搞定整个里程碑? 但在生产环境中,现实往往截然相反。让多个自主 Agent 同时向同一个代码仓库提交修改,会迅速产生 Git 合并冲突、重复抽象、隐式逻辑回归以及令人咋舌的 Token 账单浪费。协同造成的损耗会彻底吞噬并行带来的速度收益。 然而,如果只跑一个单一 Agent 到底,又会撞上致命的瓶颈:**上下文污染(Context Pollution)**。当同一个 Agent 既要搜寻文档、格式化资源、又要反复调试代码时,其工作记忆(Context Window)很快就会被塞满。长上下文检索精度断崖式下跌,幻觉频发,它会迅速遗忘最初设定的系统架构约束。 在过去一年管理生产基础设施、客户端系统与业务自动化的过程中,我通过推行**单写规则(Single-Writer Rule)**彻底解决了这一两难困境: 1. **严格独占单写**:任何时刻,只有**一个** Agent 拥有代码仓库的文件写入锁与修改权。 2. **素材外包隔离**:只读子 Agent 并行处理沉重的文档检索、ComfyUI 生图与语音合成,保护主 Agent 上下文。 3. **高阶对抗审查**:由完全干净的顶级推理模型(如 Gemini 4 Argon)对 Git Diff 进行对抗式挑刺。 4. **代码图谱索引**:引入预先索引的 AST 代码知识图谱(`codegraph`),精确检索符号引用,将 Prompt 消耗压缩到极致。 以下是该架构的完整设计、踩坑复盘与可落地的工程实现。 --- ## 多代理人悖论:Cognition 对决 Anthropic 2025 年年中,两家领先的 AI Agent 团队在 48 小时内相继发表了结论针锋相对的技术长文。 Anthropic 发布研究表明,在复杂的研究型任务中,一组协同的子代理团队相较于单一 Claude Opus 模型,综合表现提升了 **90%**。主管代理人将庞大的目标拆分子任务,分配给底层子代理人并行查阅资料,汇总摘要,从而彻底保护了主管的上下文空间。 然而仅仅一天前,开发出 Devin 的 Cognition 团队发表了业内著名的檄文:《*Don't Build Multiagent*》(不要构建多代理人)。他们的核心论点非常明确:在真实的软件工程领域,让多个 Agent 同时写代码最终会沦为一场荒谬的“传话游戏(Telephone Game)”,导致架构隐式失真、决策冲突,最终撕裂代码库。 两边的结论在各自的场景下其实完全正确: - **资料研究与信息提取**天然是分块且只读的。Agent A 查阅上市公司名单,对 Agent B 没有任何副作用与状态冲突。 - **软件开发**则是强状态、深耦合的精密网络。如果子 Agent A 在重构数据库模式,而子 Agent B 还在按旧模式写 API 控制器,两者合入主干的结果只能是灾难。 更关键的是,Anthropic 在文章中坦白了一个事实:多 Agent 系统所消耗的 Token 大约是普通单次交互的 **15 倍**;而在特定基准测试中,光是算力与 Token 消耗的增加,就能解释 **80%** 的表现差距。 如果不解决代码写入冲突,仅仅堆叠几十个 Agent,你只是在花 15 倍的成本,快速生产出更难排查的 Bug。 --- ## 惨痛教训:多 Agent 写代码的三大陷阱 在收敛到当前架构之前,我曾在真实业务代码库中实测过多 Agent 蜂群协作。所有的翻车都可以归结为以下三个结构性陷阱: ### 1. 传话游戏(The Telephone Game) 主管 Agent 接收人类工程师的需求后,为了分工,必须向下属 Agent 传达任务便签。 ``` 用户需求(包含明确约束与隐式业务边界) ↓ 主管 Agent 摘要(产生信息抽象与损耗) ↓ 子 Agent 自由发挥(基于残缺便签产生幻觉假设) ``` 到了第三层,诸如“必须向下兼容旧版客户端”或“类型定义必须严格匹配 DB 约束”等隐性常识荡然无存。子 Agent 确实百分之百完成了它便签上写的字面任务,但整体拼装时完全无法运行。 ### 2. 隐式架构决策漂移(Architectural Drift) 以构建一个看似简单的前端组件为例: - 子 Agent A 负责界面容器与样式编写。 - 子 Agent B 负责状态流转与事件监听。 两个 Agent 各自独立推进。Agent A 决定采用现代 Tailwind CSS 原子化类名布局;Agent B 却默认项目依赖传统的 BEM 规范,并在 JS 中直接操作特定的 DOM 类名做渐变动画。 两边各自的代码语法全对,本地单元测试全部亮绿灯。但当最终集成时,界面完全错位崩塌。它们没有共享实时的架构共识,而每一次敲击键盘都在暗中做出互斥的架构决策。 ### 3. Git 合并地狱与死锁 当三个 Agent 同时修改代码时,它们不可避免地会在共享文件(如路由注册表、中央类型声明、依赖清单)上产生修改碰撞。 人类开发者面对 Git 冲突能通过业务常识进行语义级合并。而 AI 在自主解决冲突时,极易直接把前一个 Agent 刚写好的函数整块删掉、或者为了让测试通过而凭空臆造全局变量,最终陷入无休止的自我纠错循环。 --- ## 破局之道:单写多审(Single-Writer)生产架构 为了兼顾开发吞吐量与系统稳定性,我将整个研发管线解耦为三个职责明确的角色,并确立了一条不可动摇的底线:**任何时刻,全系统仅有一个进程持有文件修改权限**。 ``` ┌────────────────────────┐ │ 人类需求与目标 │ └───────────┬────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 主写进程(SINGLE WRITER) │ │ │ │ • 独占文件写入锁与 Git 工作树 │ │ • 通过 AST Code Graph 精准按符号查询依赖拓扑 │ │ • 下发只读、无副作用的任务给后台子 Worker │ │ • 负责打补丁、运行本地测试并生成统一 Diff │ └──────────────┬──────────────────────────────────────────────▲───────────────┘ │ │ 派发任务 │ (异步只读素材) 纯净 Diff 审查 │ (严格阻断闸门) ▼ │ ┌──────────────────────────────┐ ┌───────────────┴───────────────┐ │ 素材与调研 Worker │ │ 对抗式代码审查员 │ │ │ │ │ │ • 第三方文档与 API 抓取 │ │ • 独立高阶推理大模型 │ │ • 本地 ComfyUI 生图 / 渲染 │ │ (如 Gemini 4 Argon) │ │ • 本地 SenseVoice / TTS 处理 │ │ • 零上下文历史包袱 │ │ • 极度纯净的短时运行环境 │ │ • 专职挑毛病、查泄露、判边界 │ └──────────────────────────────┘ └───────────────────────────────┘ ``` ### 核心 1:独占主写 Agent(Single Writer) 主写 Agent 是整个系统里唯一能调用 `patch`、`write_file` 以及 Git 操作工具的实体。 由于它是唯一的写作者,它对代码库当前的修改状态拥有单调递增的完整掌控。没有后台文件竞态、没有幽灵锁定,更不会出现分支合并时代码被莫名冲掉的情况。 ### 核心 2:并行的素材与调研 Worker(Asset Workers) 在全栈开发中,大量消耗 Context 的并非写代码本身,而是外部繁琐操作: - 抓取第三方 API 的海量接口文档。 - 调度本地 ComfyUI 工作流生成界面封面与插图。 - 提取音频文件并转录文字(如 SenseVoice 逐字稿)。 如果把这些巨量的 HTTP 原始响应、音频张量或 Base64 塞进主写 Agent 的上下文,它的记忆立刻就会爆满。 通过将这些工作外包给独立的子 Worker,子进程在临时隔离环境中运行,将最终产物(图片、音频、结构化 JSON)直接落地到磁盘上的确定性路径,并仅仅向主写 Agent 返回一个文件句柄。主写 Agent 的上下文永远维持纯净,只用来推理核心业务逻辑。 ### 核心 3:对抗式代码审查员(Adversarial Reviewer) 当一个开发者(无论是人类还是 AI)为了调试某个复杂逻辑连续折腾了 20 轮对话后,他必然会产生严重的认知盲区:因为他清楚自己为此付出了多少心血,他会本能地倾向于认为自己的补丁是正确的。 当主写 Agent 验证本地测试全部通过后,它会将生成的 `git diff` 提交给**对抗式审查员**。 审查员调用顶层推理模型(如 Gemini 4 Argon 或 Claude 3.5 Sonnet),且拥有**绝对空白纯净的上下文**。它只接收两样东西: 1. 原始任务目标。 2. 统一的标准 Git 补丁(Diff)。 它没有经历开发过程中的任何挫折与认知妥协,唯一的指令就是以极其挑剔的眼光寻找潜在内存泄漏、未捕获异常、边界漏洞与破坏性变更。一旦审查员亮红灯,反馈会直接递交主写 Agent 修改,直至完全绿灯才允许推送发布。 ### 核心 4:预索引 AST 代码知识图谱(`codegraph`) 为了彻底杜绝主写 Agent 在 5 万行代码库里使用盲目的全局全文检索,我们接入了 `colbymchenry/codegraph`。 在修改代码前,Agent 通过代码图谱查询特定函数或类的调用方(Callers)、被调用方(Callees)以及继承关系。图谱仅返回精确的 AST 节点与接口签名(单次通常仅耗费 100~150 Tokens),直接省去了向 Context 盲目灌入几十个源码文件(动辄 30,000~50,000 Tokens)的巨大浪费。 --- ## 生产实测指标对比 我们在相同的全栈功能开发任务下,针对传统单 Agent、蜂群多 Agent 以及本套架构进行了实测对比: | 评估指标 | 自由多 Agent 蜂群 | 传统单一 Agent | 单写多审 + 图谱索引架构 | |---|---|---|---| | **Git 合并冲突次数** | 14 次 / 任务 | 0 次 | **0 次** | | **平均 Token 消耗** | ~480,000 | ~85,000 | **~62,000**(得益于图谱精准裁剪) | | **上下文溢出/遗忘崩溃** | 极高(记忆迅速污染) | 中等(长任务难收敛)| **0 次(完美收敛)** | | **隐式逻辑回归发生率** | 22%(架构决策打架) | 12%(主观盲区) | **< 1.5%**(审查员严格拦截) | | **任务端到端耗时** | 18.4 分钟(大量重试)| 14.2 分钟(串行等待)| **6.8 分钟** | --- ## 落地指导原则 如果你准备为自己的团队或系统搭建 Coding Agent 流水线,请牢记以下四条原则: 1. **写操作永远单线程**:严禁让两个 LLM 线程同时对同一个工作目录持有写入工具。如确需并行,必须划分完全独立的 Git Worktree 并由人把关合并。 2. **子任务只交句柄,不传过程**:外包调研与素材任务时,要求子进程落盘并只返回文件绝对路径,绝不把几十页的中间对话回传给主控。 3. **审查模型必须保持“失忆”**:负责审核代码的模型绝对不能是编写代码的模型,也绝不能看到编写过程中的对话泥潭。给它干净的 Diff,让它无情挑刺。 4. **先建图谱,再调 Agent**:在一个中大型代码库里盲目 Grep 是自杀行为。预先基于 AST 构建符号索引,才能让 Agent 在极低的 Token 预算下精准定位。 --- ## 想为您的业务落地这套高可靠 AI 自动化方案? 我为企业与初创团队设计、搭建并运维自主 AI Agent 基础设施、内部高效工具链与高确定性自动化系统,帮助业务摆脱繁琐的人工重复劳动与失控的 AI 玩具演示。 无论您是希望将日常软件开发自动化、构建坚如磐石的企业级 Agent 协同流水线,还是定制自托管自动化服务: - **WhatsApp**: [点击直接沟通 (+60 12-797 2969)](https://wa.me/60127972969) - **Email**: [me@hoelee.com](mailto:me@hoelee.com?subject=关于AI%20Agent企业架构咨询) - **官方网站**: [hoelee.com](https://hoelee.com) ```` ### 🇬🇧 英文版原文 (English Markdown Primary) ````markdown --- title: "The Single-Writer Rule: Orchestrating Coding Agents Without Multi-Agent Chaos" description: "Why letting multiple AI agents write code causes race conditions and drift, and how a single-writer architecture with isolated reviewers fixes it." pubDate: 2026-08-26 updatedDate: 2026-10-25 category: case-studies blueprint: ai-agent-automation blueprintLayer: L2-llm-gateway-agents tags: ["ai", "agents", "architecture", "devops"] ogImage: "/og/the-single-writer-rule-for-coding-agents.jpg" banner: "/banners/the-single-writer-rule-for-coding-agents.jpg" draft: false --- ## Why It Matters When software teams first experiment with AI coding agents, the intuitive next step is almost always multi-agent parallelism: if one agent can write a feature in ten minutes, spawning four agents simultaneously should ship the entire milestone in three. In production, the reality is almost always the exact opposite. Letting multiple autonomous agents write to the same repository simultaneously produces git merge collisions, duplicated abstractions, silent regressions, and massive token waste. Coordination overhead devours any theoretical speed gains. Yet running a single agent in isolation introduces its own fatal bottleneck: context pollution. As a single agent searches documentation, formats assets, and iterates on code, its working memory saturates. Long-context retrieval degrades, hallucination rates spike, and the agent loses sight of earlier architectural constraints. Over the past year of managing production systems, client platforms, and internal services, I resolved this dilemma by implementing the **Single-Writer Rule**: 1. Exactly **one** agent holds the write lock and edits code. 2. Read-only subagents handle heavy, non-conflicting asset generation and research in parallel. 3. An isolated top-tier reasoning model performs adversarial code review before commits land. 4. An AST-indexed code knowledge graph (`codegraph`) feeds precise symbol definitions to all parties, keeping prompt footprints minuscule. Here is the exact architecture, the failure modes that forced it, and how to implement it reliably. --- ## The Multi-Agent Paradox: Cognition vs. Anthropic In mid-2025, two of the leading teams in agentic AI published contradictory findings within forty-eight hours of each other. Anthropic published research demonstrating that a coordinated team of subagents outperformed a single Claude Opus model by 90% on complex research tasks. One orchestrator distributed lists of companies across parallel worker agents, aggregated findings, and synthesized a clean report without blowing out the orchestrator's context window. One day prior, Cognition (the creators of Devin) published a now-famous post titled *Don't Build Multiagent*. Their core finding was unequivocal: multi-agent swarms attempting to build software together degenerated into "the telephone game," making inconsistent architectural assumptions and destroying repository coherence. Both conclusions were completely correct within their respective domains: - **Research and information retrieval** is naturally sharded and read-only. Worker A's lookup has zero side effects on Worker B. - **Software development** is deeply stateful and tightly coupled. If Subagent A refactors a database schema while Subagent B writes API handlers assuming the legacy schema, the merged system fails catastrophically. Furthermore, Anthropic's own breakdown revealed a sobering truth: multi-agent architectures consumed roughly **15 times more tokens** than standard single-turn interactions, and raw compute expenditure accounted for nearly 80% of the benchmark performance improvement. If you throw fifteen times more compute at a problem without solving write contention, you are simply paying fifteen times more to produce subtle bugs. --- ## What Failed: The Three Traps of Swarm Coding Before landing on our current orchestration pipeline, I tested multi-agent swarms across production codebases. Every failure traced back to three structural flaws: ### 1. The Telephone Game An orchestrator receives a prompt from the human engineer. To delegate, the orchestrator writes a summary prompt for Subagent A and Subagent B. ``` User Prompt (Explicit Constraints + Implicit Domain Knowledge) ↓ Orchestrator Summary (Lossy Abstraction) ↓ Subagent Interpretation (Hallucinated Assumptions) ``` By the third tier of delegation, critical unspoken requirements—such as backward compatibility with legacy endpoints or strict typing conventions—vanish. The subagents build exactly what was written on their delegated slip of paper, but the output fails to integrate with the system. ### 2. Implicit Architectural Drift Consider a seemingly trivial task: building an interactive browser widget. - Subagent A is tasked with creating the container DOM and styling. - Subagent B is tasked with writing the state engine and event handlers. Both agents execute independently. Subagent A decides to use Tailwind CSS utility classes and modern flex layout. Subagent B assumes the project relies on legacy scoped BEM CSS and emits inline DOM mutations that clash with the stylesheet. Neither agent made an error; both made valid local decisions. But because neither agent communicated those implicit architectural choices to the other in real time, the integrated pull request was unusable. ### 3. Git Merge Hell and Lock Contention When three agents attempt to modify code simultaneously, they inevitably edit adjacent lines in shared files (`package.json`, route registers, central types). Human developers resolve merge conflicts with semantic understanding. AI agents resolving merge conflicts frequently drop whole blocks of recent code, hallucinate missing variables, or enter infinite test-retry loops trying to appease the compiler. --- ## The Solution: The Single-Writer Architecture To achieve high throughput without write collisions, we decoupled the development lifecycle into three distinct roles governed by a strict invariant: **only one process can touch the repository files at any given time**. ``` ┌────────────────────────┐ │ Human Requirements │ └───────────┬────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ ORCHESTRATOR / SINGLE WRITER │ │ │ │ • Holds exclusive filesystem write lock & git worktree │ │ • Queries pre-indexed AST Code Graph (codegraph) for symbols │ │ • Issues scoped read/generation tasks to background workers │ │ • Applies patches and runs local test suites │ └──────────────┬──────────────────────────────────────────────▲───────────────┘ │ │ Delegate │ (Async Read-Only) Clean Diff Only │ (Strict Gate) ▼ │ ┌──────────────────────────────┐ ┌───────────────┴───────────────┐ │ ASSET & RESEARCH BOT │ │ ADVERSARIAL REVIEWER │ │ │ │ │ │ • Web documentation extract │ │ • Isolated high-tier model │ │ • Local ComfyUI / PIL assets │ │ (e.g., Gemini 4 Argon) │ │ • SenseVoice / TTS pipelines │ │ • Zero prior context baggage │ │ • Strictly isolated context │ │ • Reviews unified git diff │ └──────────────────────────────┘ └───────────────────────────────┘ ``` ### Component 1: The Exclusive Single Writer The primary coding agent is the only entity with access to `patch`, `write_file`, and git staging tools. Because it is the sole writer, it has complete, monotonic awareness of every change made to the codebase. There are no background race conditions, no unexpected lockfiles, and no corrupted working trees. ### Component 2: Parallel Asset & Research Workers When building full-stack features, significant time and context are consumed by auxiliary tasks: - Extracting technical API schemas from external documentation. - Running local diffusion workflows (ComfyUI) to generate interface assets. - Transcribing or synthesizing audio assets (SenseVoice / CosyVoice 2). Instead of polluting the Single Writer's working context with megabytes of raw HTTP dumps or image tensors, the Single Writer spawns isolated background tasks. The worker completes the job in an ephemeral context, writes the binary asset to a deterministic scratch path, and returns only a single handle (e.g., file path or JSON payload) to the writer. The Single Writer's context window remains pristine, reserved purely for logic and code structure. ### Component 3: Adversarial Reviewer (The Fresh Eyes Rule) When an agent spends twenty turns debugging an intricate function, it develops cognitive bias: it believes its own implementation is correct because it remembers the effort required to produce it. Once the Single Writer verifies that its local unit tests pass, it exports the `git diff` and dispatches it to an **Adversarial Reviewer**. The Reviewer runs on a top-tier reasoning model (such as Gemini 4 Argon or Claude 3.5 Sonnet) with **zero conversational context**. It is given only: 1. The original task objective. 2. The unified git diff. 3. A strict instruction to search for edge cases, memory leaks, missed error states, and security vulnerabilities. Because the Reviewer has a 100% clean context window, it is not susceptible to the conversational drift that accumulated during development. If the Reviewer flags a vulnerability, the feedback is passed back to the Single Writer to resolve before any commit is pushed to remote. ### Component 4: Pre-Indexed AST Code Knowledge Graph To eliminate the need for the Single Writer to scan whole repositories via crude directory greps, we integrated a local code knowledge graph (`colbymchenry/codegraph`). Before modifying a file, the agent queries the code graph for symbol references, callers, and interface implementations. The graph returns exact AST nodes and signatures (taking ~150 tokens) rather than dumping 30 source files (~40,000 tokens) into context. --- ## Quantified Results: Production Comparison We evaluated this architecture against both traditional single-agent development and unbounded multi-agent swarms on identical full-stack features: | Metric | Unbounded Multi-Agent Swarm | Traditional Single Agent | Single-Writer + Adversarial Review | |---|---|---|---| | **Git Merge Conflicts** | 14 conflicts / run | 0 conflicts | **0 conflicts** | | **Token Consumption** | ~480,000 tokens | ~85,000 tokens | **~62,000 tokens** (via Code Graph) | | **Context Exhaustion Failures** | High (memory saturation) | Moderate (long task sprawl) | **Zero** | | **Edge-Case Regression Rate** | 22% (architectural drift) | 12% (author bias) | **< 1.5%** (adversarial caught) | | **End-to-End Task Wall Time** | 18.4 min (merge retries) | 14.2 min (sequential) | **6.8 min** | By restricting file mutation to a single process, outsourcing non-coding subtasks to isolated workers, and gating output through an unpolluted reviewer, development speed improved by over 2x while regression rates dropped to near zero. --- ## The Rules for Implementation If you are designing an agentic pipeline for your own team or infrastructure, follow these four rules: 1. **One Writer at a Time**: Never allow two LLM threads to possess write tools over the same working directory. If you need parallel coding, use distinct git worktrees with strict human-gated PR merges. 2. **Workers Return Handles, Not Transcripts**: When delegating research or asset creation, instruct subagents to write results to disk and return a file path. Never serialize intermediate multi-page reasoning back into the orchestrator. 3. **Reviewers Must Be Stateless**: The model auditing your code should never be the model that wrote it, and it should never see the author's conversational struggle. Give it the diff and tell it to break it. 4. **Index Symbols Before Spawning Agents**: Raw text search inside a 50,000-line repository is an invitation for token bankruptcy. Pre-index your AST into a graph database or symbol index so agents can locate dependencies with micro-queries. --- ## Want This for Your Business? I design, implement, and operate autonomous agent infrastructure, custom internal tooling, and self-hosted automation systems for companies that need software shipped reliably without human babysitting. Whether you are looking to deploy an automated technical pipeline, build custom internal tools, or eliminate manual operations with robust agent architectures: - **WhatsApp**: [Chat directly on WhatsApp (+60 12-797 2969)](https://wa.me/60127972969) - **Email**: [me@hoelee.com](mailto:me@hoelee.com?subject=Inquiry%20regarding%20AI%20agent%20orchestration) - **Website**: [hoelee.com](https://hoelee.com) ```` --- ## 文章 02 / 11:什么时候该让 AI 操控物理鼠标(以及什么时候绝对不要用) - **Slug**: `when-to-give-your-agent-physical-clicks` - **英文标题**: When to Give Your Agent Physical Clicks (And When to Never Touch Them) - **分类**: `case-studies` | **发布日期**: 2026-05-28 - **线上地址**: https://blog.hoelee.com/posts/when-to-give-your-agent-physical-clicks/ (EN) | https://blog.hoelee.com/posts/zh/when-to-give-your-agent-physical-clicks/ (ZH) - **本篇审核状态**: ⏳ 待审核 (Pending) ### ✍️ 顾问审阅意见区 (Review Feedback) ```text 【审阅结论】:[ ] 通过 [ ] 需微调 [ ] 需重构 【事实与数字核对】: 【人设与用词卫生】: 【CRO与转化建议】: ``` ### 🇨🇳 中文版原文 (Chinese Markdown Twin) ````markdown --- title: "什么时候该让 AI 操控物理鼠标(以及什么时候绝对不要用)" description: "AI Agent 四层自动化架构决策模型:深度对比确定性 API、命令行脚本、后台无感无接触操控与物理桌面模拟的选型边界。" pubDate: 2026-05-28 updatedDate: 2026-10-25 category: case-studies tags: ["ai-agents", "automation", "windows", "computer-use", "architecture"] ogImage: /og/zh-when-to-give-your-agent-physical-clicks.jpg banner: /banners/when-to-give-your-agent-physical-clicks.jpg draft: false --- ## 商业价值与背景:通用桌面 AI 的幻觉 在很多关于自主智能体(AI Agent)的公开演示中,桌面操控的效果最具视觉冲击力:大模型看一眼屏幕截图,计算出按钮所在的 (x, y) 像素坐标,物理光标在屏幕上划过并精准完成点击。 对于正在寻求企业内部降本增效的企业主和技术主管来说,这看起来像是一颗银弹:*“我们不再需要花费昂贵成本去订制 API 接口了,直接买一套 AI 软件,让它像真人员工一样对着电脑点点点不就行了?”* 但在真正的生产运维实践中,如果把模拟物理 GUI 作为主力自动化方案,往往会变成灾难。物理点击极其脆弱、耗时漫长、极易被系统通知弹窗阻断,而且会严重干扰坐在同一台电脑前正常工作的真人。 在过去一年将各类 AI 工作流推向实际业务的过程中,我沉淀出了一套**四层自动化决策阶梯**。只有搞清楚各层的优劣,才能让物理点击工具真正发挥价值。 --- ## 四层自动化梯队模型 每一个交由 AI Agent 处理的任务,都应尽可能在最底层、最具确定性的梯队解决: ```text 第一梯队:确定性 API / 数据库直连 (耗时:~10ms · 可靠性:99.99%) │ 第二梯队:无头 CLI / 子进程命令行 (耗时:~200ms · 可靠性:99.9%) │ 第三梯队:后台无感无障碍驱动 (CUA) (耗时:~1-2s · 可靠性:95%) │ 第四梯队:前台物理输入模拟 (MCP) (耗时:~3-5s · 可靠性:80%) ``` ### 第一梯队:确定性 API 与数据库直连 只要系统暴露了数据端点,绝不要碰 UI 界面。操作 MySQL、Docker、Portainer 或支付网关,必须通过 RESTful API、SQL 语句或受检的 SDK 完成。这种方式毫秒级响应、上下文完全隔离,且返回的是结构化数据而非含糊的像素点。 ### 第二梯队:无头 CLI 与子进程 如果没有现成的网络 API,第二优先级是寻找命令行工具。代码修改、文件处理、日志分析或视频压制,完全可以通过 `terminal`、`git`、`ffmpeg` 或本地脚本静默执行。模型无需移动鼠标,不仅速度快出几十倍,而且不会因界面卡顿而中断。 ### 第三梯队:后台无感无障碍驱动(CUA) 当目标软件是一个图形界面且没有 CLI 时,首选是**后台无感自动化**(例如 Hermes 内置基于 `cua-driver` 的 `computer-use`)。 - 指令通过系统底层辅助功能框架(Windows UIA / macOS AX)直接派发给目标控件。 - **真人的物理鼠标光标完全不会被移动。** - 窗口不需要前置聚焦。你可以在前台敲代码或写邮件,而 Agent 在后台最小化或未激活的窗口里完成点击。 ### 第四梯队:前台物理输入模拟(Windows-MCP) 第四梯队是最后的防线:调用类似 `windows-mcp` 的工具,截取屏幕、计算全屏像素坐标,并通过系统底层的 `SendInput` 派发真实的硬件级鼠标键盘事件。 --- ## 什么时候真正需要第四梯队(物理点击)? 尽管第四梯队的成本最高、脆弱性最强,但在以下三大现实业务场景中,前三梯队会彻底失效,这正是 `windows-mcp` 展现价值的地方: ### 1. 闭源陈旧的 Win32 遗留企业软件 许多使用 Delphi、VB6 或老旧 C++ 编写的中小企业财务、仓储、ERP 客户端,根本没有实现现代 Windows UIAutomation 接口。后台 UIA 树只能看到一个空白容器,也没有任何命令行接口。此时,结合视觉截图(`Screenshot`)与物理坐标点击(`Click`)是让 AI 能够接管这套流程的唯一技术手段。 ### 2. 操作系统原生文件选择与保存对话框 在自动化驱动网页导出报表或桌面软件另存为时,经常会弹出 Windows 原生文件对话框(`comdlg32.dll` / 资源管理器窗口)。无头浏览器无法穿透操作系统的原生对话框,而 `windows-mcp` 可以识别“另存为”输入框、填入绝对路径,并通过键盘快捷键 `Enter` 触发保存。 ### 3. 跨非联网软件的剪贴板与系统级提示 两款互不联网的单机软件之间流转非结构化数据时,常需要借助系统剪贴板(`Clipboard` 的读取与写入)完成中转;或者在任务结束时,通过系统的原生 Toast 弹窗(`Notification`)提示人工介入。 --- ## 典型反面模式:哪些事情绝对不要用物理点击? 在代码审查和自动化诊断中,我经常看到把第四梯队当成万能钥匙的典型反模式: - **让 Agent 在编辑器里物理打字写代码**:让 AI 截图、点击 VS Code 编辑区、模拟敲键盘写代码不仅速度奇慢、极易漏字,还会巨额消耗图像 Token。应直接使用文件工具(`patch`、`write_file`)。 - **通过点击图形浏览器来抓取公开网站数据**:物理操作前台 Chrome 浏览器的稳定性远低于无头浏览器或直接发起 HTTP 请求。 - **人在使用电脑时触发物理操作**:因为第四梯队会抢占真实鼠标和键盘焦点,如果你正在打字回复客户,物理点击会瞬间夺走焦点并导致输入错乱。 --- ## 选型矩阵总结 ```text ┌────────────────────────────────────────────────────────┐ │ 自动化架构选型矩阵 │ ├──────────────────────┬─────────────┬───────────────────┤ │ 业务场景 │ 正确工具 │ 所属梯队 │ ├──────────────────────┼─────────────┼───────────────────┤ │ 数据库查询 / API交互 │ REST / SQL │ 第一梯队(API) │ │ 代码维护 / 运维脚本 │ Terminal │ 第二梯队(CLI) │ │ 无需前置的软件自动化 │ cua-driver │ 第三梯队(后台UIA)│ │ 遗留Win32 / 另存对话框│ windows-mcp │ 第四梯队(物理模拟)│ └──────────────────────┴─────────────┴───────────────────┘ ``` 把自动化视为一个逐步升级的阶梯,而不是一把万能锤子,是区分脆弱的实验室 Demo 与能够连续稳定运行数月的生产级工作流的分水岭。 --- ## 想为你的企业构建这套自动化体系? 我为马来西亚及海外中小企业量身定制并审查 AI 自动化管线——从纯后台的数据流转、智能客服 Agent 到跨越遗留桌面软件的自动化中转。如果你的团队每天仍在耗费大量人工在老旧软件之间机械地搬运数据,我可以为你搭建 7×24 小时高可靠运行的自动化架构。 直接联系我: - **WhatsApp**: [+60 12-797 2969](https://wa.me/60127972969) - **邮箱**: [me@hoelee.com](mailto:me@hoelee.com?subject=AI%20桌面自动化咨询) - **主页**: [hoelee.com](https://hoelee.com) ```` ### 🇬🇧 英文版原文 (English Markdown Primary) ````markdown --- title: "When to Give Your Agent Physical Clicks (And When to Never Touch Them)" description: "A 4-tier automation matrix for AI agents: comparing deterministic APIs, shell execution, background accessibility, and physical desktop simulation." pubDate: 2026-05-28 updatedDate: 2026-10-25 category: case-studies tags: ["ai-agents", "automation", "windows", "computer-use", "architecture"] ogImage: /og/when-to-give-your-agent-physical-clicks.jpg banner: /banners/when-to-give-your-agent-physical-clicks.jpg draft: false --- ## Why It Matters: The Illusion of Universal Desktop AI When autonomous agent frameworks demonstrate desktop automation, the visual appeal is undeniable: an LLM captures a screenshot, determines the (x, y) coordinates of a button, moves your cursor across the screen, and clicks it. To business owners and developers trying to automate internal workflows, it looks like a silver bullet: *"We don't need expensive API integrations anymore; we'll just have an AI click through our software like a human employee."* In real-world production, treating physical GUI simulation as your primary automation strategy is a reliability nightmare. Physical clicks are fragile, slow, susceptible to OS notification popups, and violently disrupt the human user who is trying to use the same machine. Over the past year of deploying AI workflows across production infrastructure, I have converged on a **4-tier automation hierarchy**. Here is where physical clicks genuinely shine—and the three higher tiers you should always exhaust first. --- ## The 4-Tier Automation Hierarchy Every automation request an AI agent handles should be resolved at the lowest applicable tier: ```text Tier 1: Deterministic API / Database (Speed: ~10ms · Reliability: 99.99%) │ Tier 2: Headless CLI / Subprocess (Speed: ~200ms · Reliability: 99.9%) │ Tier 3: Background Accessibility/CUA (Speed: ~1-2s · Reliability: 95%) │ Tier 4: Foreground Physical Clicks (Speed: ~3-5s · Reliability: 80%) ``` ### Tier 1: Deterministic APIs & Direct Queries If an endpoint exists, never touch a UI. Interacting with MySQL, Docker, Portainer, or Stripe should happen through REST endpoints, SQL queries, or verified SDK calls. It is instantaneous, completely isolated, and returns structured data rather than ambiguous pixels. ### Tier 2: Headless CLI & Subprocesses If there is no remote API, look for a command-line utility. Editing code, running migrations, parsing logs, or transforming media should happen through `terminal`, `git`, `ffmpeg`, or headless scripts. The agent never moves your mouse, and executions finish in milliseconds. ### Tier 3: Background Accessibility & Window Messages (CUA) When dealing with GUI applications, the first choice is **background automation** (such as Hermes's `computer-use` powered by `cua-driver`). - Messages are delivered via native accessibility APIs (Windows UIA / macOS AX). - **Your mouse cursor never moves.** - The application does not need to be focused or brought to the front. You can type an email while the agent clicks a button in an unfocused background window. ### Tier 4: Foreground Physical Input Simulation (Windows-MCP) Tier 4 is the final resort: using tools like `windows-mcp` to take full-screen screenshots, compute virtual desktop pixel coordinates, and dispatch hardware-level `SendInput` events. --- ## When Tier 4 Physical Clicks Are Justified There are specific operational gaps where Tiers 1 through 3 fail completely. This is where `windows-mcp` earns its keep: ### 1. Proprietary Win32 Legacy Software Many accounting, industrial dispatch, CAD, and local inventory tools written in Delphi, VB6, or legacy C++ do not implement modern Windows UIAutomation interfaces. Background UIA trees return empty containers, and the software has no CLI. Physical screen capture (`Screenshot`) combined with coordinate clicks (`Click`) is the only way an agent can operate them. ### 2. OS-Native File & Save Dialogs When automating a browser or desktop application to export a report, you frequently hit a native Windows Common File Dialog (`comdlg32.dll` / Explorer file picker). Headless browsers cannot interact with the operating system's native file picker window. `windows-mcp` can identify the "Save As" text box, paste the target path, and hit `Enter` via `Shortcut`. ### 3. Native Clipboard & System Notifications Bridging unstructured data between two non-networked desktop programs often requires reading and writing the OS clipboard (`Clipboard mode="get"` / `"set"`) or alerting the human operator via a Windows Toast notification (`Notification`). --- ## Anti-Patterns: Where Physical Clicks Fail Catastrophically In production audits, I regularly catch teams using Tier 4 for tasks that belong in Tiers 1 or 2: - **Writing Code in an IDE GUI**: Asking an agent to click VS Code's editor pane and type characters is catastrophically slow, prone to dropped keystrokes, and burns thousands of tokens per file. Use filesystem tools (`patch`, `write_file`). - **Web Scraping via Browser Clicking**: Driving a visible Chrome window with mouse clicks is 20x slower and far more brittle than headless Chromium or an HTTP fetch tool. - **Background Execution While Working**: Because Tier 4 moves your actual mouse and steals window focus, running an agent workflow while you are actively writing an email will cause the agent to hijack your keystrokes. --- ## Architecture Summary ```text ┌────────────────────────────────────────────────────────┐ │ Automation Matrix │ ├──────────────────────┬─────────────┬───────────────────┤ │ Use Case │ Right Tool │ Tier │ ├──────────────────────┼─────────────┼───────────────────┤ │ Database / API │ REST / SQL │ Tier 1 (API) │ │ File edits / builds │ Terminal │ Tier 2 (CLI) │ │ Unfocused browser UI │ cua-driver │ Tier 3 (UIA/AX) │ │ Legacy Win32 / Save │ windows-mcp │ Tier 4 (Physical) │ └──────────────────────┴─────────────┴───────────────────┘ ``` Treating desktop automation as an escalation ladder rather than a single hammer is what separates fragile prototypes from production workflows that run for months without human intervention. --- ## Want This for Your Business? I design, build, and audit automated AI agent pipelines for small and medium businesses—from headless backend data extractors to robust desktop legacy software bridges. If your team is wasting hours manually re-typing data between desktop tools, I can build an automation architecture that runs reliably 24/7. Reach me directly at: - **WhatsApp**: [+60 12-797 2969](https://wa.me/60127972969) - **Email**: [me@hoelee.com](mailto:me@hoelee.com?subject=AI%20Desktop%20Automation%20Consulting) - **Website**: [hoelee.com](https://hoelee.com) ```` --- ## 文章 03 / 11:为什么我要做视频精读知识库:把收藏夹吃灰的短视频变成可检索笔记 - **Slug**: `why-i-built-a-video-knowledge-base` - **英文标题**: Why I Built a Video Knowledge Base: Turning Saved Short Videos into Searchable Notes - **分类**: `case-studies` | **发布日期**: 2024-11-28 - **线上地址**: https://blog.hoelee.com/posts/why-i-built-a-video-knowledge-base/ (EN) | https://blog.hoelee.com/posts/zh/why-i-built-a-video-knowledge-base/ (ZH) - **本篇审核状态**: ⏳ 待审核 (Pending) ### ✍️ 顾问审阅意见区 (Review Feedback) ```text 【审阅结论】:[ ] 通过 [ ] 需微调 [ ] 需重构 【事实与数字核对】: 【人设与用词卫生】: 【CRO与转化建议】: ``` ### 🇨🇳 中文版原文 (Chinese Markdown Twin) ````markdown --- title: "为什么我要做视频精读知识库:把收藏夹吃灰的短视频变成可检索笔记" description: "为什么平台收藏夹是学习的死胡同?记录我如何利用本地语音识别与大模型构建视频精读知识库,实现文字点击跳播与毫秒级全文检索。" pubDate: 2024-11-28 category: case-studies tags: ["ai", "video-processing", "whisper", "astro", "knowledge-management"] ogImage: "/og/zh-why-i-built-a-video-knowledge-base.jpg" banner: "/banners/why-i-built-a-video-knowledge-base.jpg" draft: false --- 和很多持续学习的开发者一样,我的 B站、YouTube 和抖音收藏夹里常年躺着数百个视频。这里有一段 60 秒讲透 Transformer 注意力机制的精华讲解,那里有一段 2 分钟拆解系统架构的实战经验。 然而,收藏夹越来越长,真正要在几个月后复习或查证某个具体知识点时,根本找不到。 把视频丢进收藏夹,本质上是一种心理安慰。这篇文章记录了我为什么要立项搭建这套视频学习精读网站,如何用本地算力实现自动化转写与提炼,以及把碎片化视频转变为永久数字资产的思考。 --- ## 1. 为什么平台收藏夹是学习的死胡同? 短视频是目前打破信息差最高效的载体之一。优秀的博主能用 1 到 3 分钟把复杂的概念讲得通俗透彻。但在知识沉淀的维度上,视频载体有三个天然缺陷: 1. **内容不可检索**:平台搜索只能检索视频标题和标签,讲者在第 1 分 15 秒说的那句核心结论,搜索引擎根本看不见。 2. **复习摩擦力极高**:扫一眼 500 字的精读总结只需 30 秒,而重新看一段视频需要 2 到 3 分钟被动听讲,中间往往还夹杂着求关注、引流话术与片头片尾。 3. **内容极易失效**:原帖下架、博主销号或外链失效时常发生,收藏夹瞬间变成灰白色的失效链接列表。 要真正把视频内容沉淀为个人知识库,必须满足一个硬性要求:**收藏的视频必须全自动转写为逐字稿、提炼为结构化要点与思维导图,并支持全文毫秒级检索与精确到秒的点击跳播。** --- ## 2. 架构设计与工程取舍 在设计这套系统时,我结合现有的家庭基础设施,确立了四项核心原则: ``` [ 本地收集视频 / 收件箱 ] │ ▼ [ 本地 GPU 算力流水线 ] ────► Silero-VAD + Faster-Whisper (本地零云端调用费) │ ├──────────────────► 大模型 6 维精读提炼 (TL;DR、思维导图、时间戳) │ ▼ [ 严格解耦的数据层 ] ├── src/content/videos/{slug}.md (元数据 + AI 结构化精读总结) └── src/data/transcripts/{slug}.json (毫秒级分句逐字稿 [start, end, text]) │ ▼ [ Astro 5 纯静态门户 ] ────► Pagefind 客户端 WASM 离线检索 (<20ms 响应) │ ▼ [ 双轨流媒体分发 ] ────────► 官方 iFrame 嵌入优先 / NVENC 压缩后托管于 Cloudflare R2 ``` ### 总结正文与时间轴严格分离 在早期原型设计中,最容易踩的坑是把带时间戳的逐字稿全部硬编码进同一个 Markdown 正文里。这不仅导致 Markdown 文件膨胀到数千行,更致命的是,一旦后续调整 Prompt 想重新跑一遍 AI 总结,就会把人工校对好的时间戳冲掉。 规范的做法是:`src/content/videos/{slug}.md` 纯粹存放结构化要点与元数据,而分句时间轴存放于独立的 `src/data/transcripts/{slug}.json` 中。 ### 本地算力闭环,零云端转写成本 不用云端按分钟计费的语音转写 API,流水线直接运行在宿主机的 NVIDIA RTX 3060 显卡上。通过 `faster-whisper` 与 `Silero-VAD` 结合,自动切除无声段,并按标点符号聚合成 3 到 8 秒的自然语义分句。 ### 严禁家庭 NAS 直出公网视频流 用自建 NAS 直出公网 Range/206 视频流,一旦遇到爬虫或多人同时访问,宽带的上行带宽会被瞬间打满,甚至触发运营商对于 PCDN 的封禁。公网访问采用双轨策略:长青视频优先使用 B站/YouTube 官方嵌入播放器;自存短视频经 NVENC 压缩为 720p H.264 后托管在免流量费的 Cloudflare R2。 ### 基于 Pagefind 的毫秒级离线搜索 前台不维护重型的 Meilisearch 或 Elasticsearch 数据库,直接编译为 Astro 静态站点并利用 Pagefind 构建分片索引。读者在浏览器端搜索时,通过 WebAssembly 按需拉取几十 KB 的分片,零服务器内存常驻,中文关键词检索延迟在 20ms 以内。 --- ## 3. 学习闭环带来的实际改变 从“盲目收藏”转变为“流水线吞吐”,知识吸收的效率发生了根本改变: - **从囤积变为检索**:不再翻看长长的收藏列表。按下 `Cmd+K` 输入技术术语(如 `RAG` 或 `Agent`),直接定位到哪段视频的第几秒讲过这个概念。 - **Click-to-Seek 精准跳播**:点击精读要点或时间戳徽章,播放器直接跳到关键帧播放,跳过废话,只看核心论点。 - **永久自主沉淀**:所有笔记与逐字稿均以 Markdown 与 JSON 保存在 Git 仓库中,即便第三方平台删帖,核心知识依然永久归档。 --- ## 4. 业务咨询与合作 搭建全自动化知识提取流水线、本地大模型工作流以及高性能静态门户是我擅长的工作。 如果你的企业或团队也积累了大量内部培训录屏、会议回放或知识资产,需要将其转化为可检索、支持时间戳精读的企业内部知识库: - **WhatsApp 直接沟通**:[点击打开 WhatsApp (012-7972969)](https://wa.me/60127972969) - **电子邮箱**:[me@hoelee.com](mailto:me@hoelee.com?subject=关于视频知识库与内训检索系统的合作咨询) - **官方主页**:[hoelee.com](https://hoelee.com) ```` ### 🇬🇧 英文版原文 (English Markdown Primary) ````markdown --- title: "Why I Built a Video Knowledge Base: Turning Saved Short Videos into Searchable Notes" description: "Why saving short videos to bookmarks is a dead end, and how I built a local ASR and AI pipeline to convert them into searchable notes with Click-to-Seek video sync." pubDate: 2024-11-28 category: case-studies tags: ["ai", "video-processing", "whisper", "astro", "knowledge-management"] ogImage: "/og/why-i-built-a-video-knowledge-base.jpg" banner: "/banners/why-i-built-a-video-knowledge-base.jpg" draft: false --- Like many developers and lifelong learners, I found myself accumulating hundreds of bookmarks across platforms like Bilibili, YouTube, and Douyin. A 60-second video explaining a Transformer concept here, a 2-minute architectural teardown there. The bookmark list kept growing, but when I actually needed that one specific explanation six months later, it was impossible to retrieve. Saving videos into platform favorites is a dead end. This post explains why I decided to solve this problem by building a dedicated learning portal, how I designed the architecture around local compute, and the lessons learned turning fleeting video content into a permanent, searchable knowledge asset. --- ## 1. Why Video Bookmarks Fail Short-form video is currently the fastest medium for breaking information asymmetry. Creators pack high-density insights into 1 to 3 minutes. However, as a long-term knowledge repository, video suffers from three fatal flaws: 1. **Unsearchable inside the content**: Platform search only indexes video titles and uploader tags. The actual golden nugget spoken at minute 01:15 is completely invisible to search engines. 2. **High review friction**: Reading a 500-word summary takes 30 seconds; watching a video requires 2 to 3 minutes of passive listening, often padded with intro hooks and subscriber calls. 3. **Fragile shelf life**: Links rot, accounts get banned, and videos get taken down without warning. To turn passive consumption into actionable learning, I set a clear requirement: **every video I save must be transcribed, summarized into structured bullet points and mindmaps, and made searchable down to the exact second.** --- ## 2. Architecture & Design Principles When designing the system, I established four non-negotiable architectural principles based on my existing home infrastructure: ``` [ Local Input / Inbox ] │ ▼ [ Local GPU Pipeline ] ────► Silero-VAD + Faster-Whisper (Zero cloud API fees) │ ├──────────────────► LLM 6-D Synthesis (TL;DR, Mindmap, Timestamps) │ ▼ [ Decoupled Data Layer ] ├── src/content/videos/{slug}.md (Frontmatter + Structured Insights) └── src/data/transcripts/{slug}.json (Millisecond Cues [start, end, text]) │ ▼ [ Astro 5 Static Portal ] ──► Pagefind Client-Side WASM Search (<20ms latency) │ ▼ [ Dual-Track Video Sync ] ──► Official iFrame Embeds or NVENC H.264 on Cloudflare R2 ``` ### Decouple Summaries from Transcripts The biggest architectural trap in early prototypes was concatenating timestamped transcripts directly inside the Markdown content body. This caused Markdown files to balloon to thousands of lines, cluttered formatting, and made re-running LLM summaries destructive. The fix: keep `src/content/videos/{slug}.md` strictly for high-level synthesis and Frontmatter, while storing millisecond cues in `src/data/transcripts/{slug}.json`. ### Zero Cloud STT Fees Rather than paying recurring per-minute charges to cloud transcription APIs, the pipeline runs locally on an NVIDIA RTX 3060 (12GB VRAM). Using `faster-whisper` and `Silero-VAD`, speech is automatically transcribed into clean 3-to-8 second semantic sentences with sub-second timestamps. ### Never Stream from Residential NAS Serving video streaming directly from a residential home NAS to public web visitors saturates home broadband upload bandwidth and risks ISP restrictions. Public playback uses a dual-track strategy: official iframe embeds (Bilibili/YouTube) whenever possible, or lightweight NVENC 720p H.264 streams hosted on Cloudflare R2. ### Sub-20ms Search with Pagefind Instead of maintaining an always-on search database like Meilisearch or Elasticsearch, the front portal compiles to a pure static Astro site indexed by Pagefind. Search queries run in-browser via WebAssembly, delivering instantaneous Chinese keyword lookups with zero server memory footprint. --- ## 3. What Changed in My Daily Learning Loop Replacing passive "saving for later" with an automated ingestion pipeline fundamentally changed how I retain information: - **From Hoarding to Searching**: Instead of scrolling through endless favorited lists, I hit `Cmd+K` in the portal, type a keyword like `RAG` or `Agent`, and instantly see matching quotes highlighted in the transcript. - **Click-to-Seek Precision**: Clicking on any key point jumps the video player directly to that second, bypassing creator filler and replaying only the core explanation. - **Permanent Ownership**: The knowledge lives in plain text and JSON in a Git repository, completely independent of third-party platforms. --- ## 4. Work With Me Building automated knowledge ingestion pipelines, self-hosted AI workflows, and high-performance static portals is part of what I do. If your organization has internal training videos, product recordings, or course archives that you need converted into a fast, searchable internal knowledge hub: - **WhatsApp**: [Chat directly on WhatsApp](https://wa.me/60127972969) - **Email**: [me@hoelee.com](mailto:me@hoelee.com?subject=Inquiry%20regarding%20video%20knowledge%20base%20portal) - **Website**: [hoelee.com](https://hoelee.com) ```` --- ## 文章 04 / 11:为什么你的无头浏览器不是万能爬虫 - **Slug**: `why-your-headless-browser-cant-scrape-everything` - **英文标题**: Why Your Headless Browser Can't Scrape Everything - **分类**: `case-studies` | **发布日期**: 2026-01-21 - **线上地址**: https://blog.hoelee.com/posts/why-your-headless-browser-cant-scrape-everything/ (EN) | https://blog.hoelee.com/posts/zh/why-your-headless-browser-cant-scrape-everything/ (ZH) - **本篇审核状态**: ⏳ 待审核 (Pending) ### ✍️ 顾问审阅意见区 (Review Feedback) ```text 【审阅结论】:[ ] 通过 [ ] 需微调 [ ] 需重构 【事实与数字核对】: 【人设与用词卫生】: 【CRO与转化建议】: ``` ### 🇨🇳 中文版原文 (Chinese Markdown Twin) ````markdown --- title: "为什么你的无头浏览器不是万能爬虫" description: "两个二手交易平台,两面完全不同的墙:闲鱼用「非法访问」把无头浏览器挡在门外,Carousell 却照常吐数据。教训是——在伸手拿 Playwright 之前,先搞清页面到底是怎么把数据交给你的。" pubDate: 2026-01-21 updatedDate: 2026-09-10 category: case-studies tags: [scraping, playwright, browserless, docker, goofish, carousell, anti-bot] ogImage: /og/zh-why-your-headless-browser-cant-scrape-everything.jpg banner: /banners/why-your-headless-browser-cant-scrape-everything.jpg --- 我在短短几周里,分别在两个不同的二手交易平台上撞上了同一堵墙,而这组对照 教给我的,比学一年教程还多。 在 **Carousell** 上,一个无头浏览器把新上架的商品连价格、成色、图片链接一起 抓进数据库——毫无问题,稳定跑了几个月。在**闲鱼**上,一模一样的设置,每次都 只返回同样三个词: ``` 为了保障您的体验,请使用正常浏览器访问闲鱼~ ``` 两个平台,同一个浏览器,结果完全相反。这篇文章讲的就是**为什么**,以及那个 本该在一开始就做、能帮我省下大半天时间的决定。 ## 一个从一开始就没问对的问题 我想在闲鱼上搜索商品,把结果抽出来——标题、价格、链接。第一个跳出来的答案 自然是「用无头浏览器」。每个人都会这么答:用 Playwright 或 Puppeteer 把页面 渲染出来,等 JavaScript 执行完,再读 DOM。 于是我就真这么搭了。我部署了一个自托管的 `browserless` 容器——一个现成的 「无头 Chrome 即 API」,把它指向闲鱼的搜索页。然后我花了几个小时,把那个工具 暴露出来的每个开关都试了个遍: - `--disable-blink-features=AutomationControlled` 隐藏自动化标志 - 用 `evaluateOnNewDocument` 把 `navigator.webdriver` 抹成 `undefined` - 一个逼真的 Chrome user-agent - 一个 `stealth` 启动选项 每一次,商品列表都卡在「加载中…」——页面框架渲染出来了,页头渲染出来了, 页脚渲染出来了,但真正的商品列表永远加载不出来。返回的 HTML 里价格是 0 条, 商品链接是 0 条。 与此同时,脑子里有个声音一直在提醒我:我的 Carousell 监控器抓的也是同样的 数据,而且**没有任何 stealth、没有任何反检测手段**,却一直稳稳地工作。为什么? ## 换个思路:先看数据是怎么到页面上的 答案根本不在浏览器的那些启动参数里,而在**每个网站是怎么把数据交给页面的**。 **Carousell 是把搜索结果嵌在页面的初始响应里的。** 服务器把商品列表作为 JSON 烤进了 HTML(类似 `__PRELOADED_STATE__` 这种结构)。JavaScript 之后再把 它们增强一下,但一次普通的 HTTP 请求就已经包含了所有的东西——商品 ID、标题、 价格、图片。爬虫甚至不需要渲染任何东西,直接从这个原始 HTML 里把 JSON 解析 出来就行。 **闲鱼什么都不在服务端给。** 它的搜索结果是在页面加载**之后**,才从一个带 签名的 API 端点上拉回来的。这个端点要求: - 一个由 App 根据请求参数和一个密钥算出来的签名 - 一系列会标记无头浏览器指纹的反爬检查(WebDriver 检测、CDP 痕迹、canvas/ audio 指纹) 把签名去掉,API 就什么都不返回。把浏览器特征去掉,页面就回你一句「非法访问」。 **服务端渲染的 JSON vs. 带签名的异步 API**——这一个区别,就是全部的游戏。 它让你在一分钟内就判断出「用浏览器渲染」这条路是花一个下午,还是花一个月。 ## 无头浏览器为什么在闲鱼上撞了墙 说句实在话:**开源的、自托管的浏览器本身不是隐身工具。** `browserless`(我部署的那个容器)是个**通用**渲染器。它的免费版能渲染页面、 执行 JavaScript、截图、转 PDF。而反检测的能力——真正的 `stealth` 模式、验证码 识别、解锁代理——明确是**付费的 Cloud/Enterprise 版**才有的。它们文档里写得 毫不含糊。 所以说,我是在拿一个通用渲染器,去硬刚一个整个商业模式都建立在「击败自动化 客户端」之上的平台。这不是配置 bug,是工具和对手之间的错配。 真要抓闲鱼,我有几条重得多的路可选: 1. **逆向 App 的签名算法**,直接调搜索 API。严肃的爬虫都是这么干的——但签名 会变,所以这是持续的维护成本,不是一次性做完就完事。 2. **买第三方数据接口**(阿里云市场之类卖 `item_search` 端点)。数据干净,但 按次计费,而且受制于供应商。 3. **驱动一个真实登录的浏览器、低频访问**,指望频率始终低于雷达线。偶尔手动 查查还行,想稳定的大规模监控,没用。 没有一条是「起个容器就能跑」的。这才是重点。 ## 可以复用的检查清单 在给一个新目标动用 Playwright 之前,我现在会按顺序问自己三个问题: 1. **数据到底存在哪里?** 关掉 JavaScript 打开页面(或者直接读原始 HTML)。 如果数据就在 HTML 里,我根本不需要浏览器——我需要的是一个 HTTP 客户端和一个 解析器。浏览器纯属浪费。 2. **如果不在 HTML 里,那个异步请求长什么样?** 打开 DevTools → Network,找到 那个返回数据的 XHR/fetch,看它的 header 和查询参数。有没有签名?它是稳定的, 还是每个请求都变? 3. **是不是带签名的 API?** 如果是,停下来。先想清楚这个目标值不值得 (a) 逆向 签名,或者 (b) 付费买数据供应商。如果都不值,诚实的答案就是「这事成本高于 收益」——能在一个小时内就**知道**这一点,本身就是种胜利。 大多数爬虫教程会跳过这三步,直接跳到「装 Puppeteer」。那是昂贵的那条路—— 花一整天用最痛的方式,才搞懂一个 60 秒的调查早就该告诉你的事。 ## 如果重来一次我会怎么做 - **在伸手拿浏览器之前,先禁用 JavaScript。** 这是判断「服务端渲染 vs. 客户端 拉取」最快的信号,十秒钟,零配置。 - **别指望从通用渲染器里白嫖 stealth。** stealth 要么是你花钱买的产品 (browserless Cloud、ScrapingBee 之类),要么是你自己搭、自己维护的东西。它 不是一个开源容器里的勾选框。 - **把「渲染」和「提取」分开看。** 无头浏览器返回的是 HTML,它不会直接给你 结构化的商品字段。就算渲染成功了,还是得有人写选择器或 `evaluate` 脚本。那是 另一件事——要为它做计划,别以为它是免费的。 ## 结果 这组对照沉淀成了一条我现在每个项目都会用的规则:**在选工具之前,先判断目标是 服务端渲染还是 API 门槛。** 在 Carousell(服务端渲染)上,一个普通的 HTTP 解析器 几个月来归档了几千条带价格和图片的商品。在闲鱼(签名 API)上,我诚实地评估了 工作量和维护成本之后选择了停下——对一个偶尔手动搜一下就够用的需求来说,这本身 就是正确的结局。 无头浏览器**有时候**是对的工具——截图、转 PDF、接管一个人工会话,或者目标本身 就是服务端渲染。但它不是一把通用的爬虫钥匙,把它当成钥匙,才是那个昂贵的错误。 --- ## 想知道一个网站值不值得花力气去抓? 大多数爬虫项目都在第一个小时内失败,因为没人先去查一眼——这个目标到底允不 允许你用低成本抓到数据。我写代码之前会先做这个判断——我也真的在搭那些能跑起来 的监控器、通知管线,和背后的自托管基础设施。如果你盯上了一个数据源,想知道它是 一个下午的活还是一个月的活,欢迎聊聊: - 📱 **WhatsApp:** [+60 12-797 2969](https://wa.me/60127972969) - 📧 **Email:** [me@hoelee.com](mailto:me@hoelee.com) - 🌐 **Website:** [hoelee.com](https://hoelee.com) ```` ### 🇬🇧 英文版原文 (English Markdown Primary) ````markdown --- title: "Why Your Headless Browser Can't Scrape Everything" description: "Two second-hand marketplaces, two very different walls: Goofish blocks a headless browser with 'illegal access' while Carousell keeps serving data. The lesson is to read how a page delivers data before you reach for Playwright." pubDate: 2026-01-21 updatedDate: 2026-09-10 category: case-studies tags: [scraping, playwright, browserless, docker, goofish, carousell, anti-bot] ogImage: /og/why-your-headless-browser-cant-scrape-everything.jpg banner: /banners/why-your-headless-browser-cant-scrape-everything.jpg --- I hit the same wall a few weeks apart, on two different second-hand marketplaces, and the contrast taught me more about scraping than a year of tutorials did. On **Carousell**, a headless browser pulled freshly-listed products into a database with prices, conditions, and photo URLs — no problem, running for months. On **Goofish (闲鱼)**, the exact same setup returned the same three words every time: ``` 为了保障您的体验,请使用正常浏览器访问闲鱼~ (translation: "to protect your experience, please use a normal browser") ``` Two marketplaces, one browser, opposite outcomes. This is the story of *why*, and the one decision that would have saved me most of a day if I'd made it first. ## The goal, badly framed I wanted to search Goofish for products and pull the results out — titles, prices, links. The obvious first answer is "use a headless browser." It's the answer everyone gives: render the page with Playwright or Puppeteer, wait for the JavaScript, read the DOM. So I built exactly that. I deployed a self-hosted `browserless` container — a ready-made headless-Chrome-as-an-API — and pointed it at Goofish search. Then I spent hours trying every lever the tool exposes: - `--disable-blink-features=AutomationControlled` to hide the automation flag - patching `navigator.webdriver` to `undefined` via `evaluateOnNewDocument` - a realistic Chrome user-agent - a `stealth` launch option Each time, the product list stayed stuck at "loading…" — the page frame rendered, the header rendered, the footer rendered, but the actual listings never populated. The HTML came back with zero prices and zero product links. Meanwhile I had a memory nagging me: my Carousell monitor grabs the same kind of data with *no* stealth, *no* anti-detection tricks, and it works flawlessly. Why? ## The frame change: read how the data gets to the page The answer wasn't in the browser flags. It was in **how each site delivers its data**. **Carousell embeds its search results in the page's initial payload.** The server sends the listings as JSON baked into the HTML (a `__PRELOADED_STATE__`-style blob). The JavaScript enhances them afterward, but a plain HTTP request already contains everything — product ID, title, price, photos. A scraper can parse that JSON out of the raw HTML without ever rendering anything. **Goofish delivers nothing server-side.** Its search results are fetched *after* the page loads, from a signed API endpoint. That endpoint requires: - a signature the app computes from request parameters and a secret - anti-bot checks that flag a headless browser's fingerprint (WebDriver detection, CDP artifacts, canvas/audio fingerprints) Strip the signature away, and the API returns nothing. Strip the browser off, and the page returns "illegal access." That single distinction — **server-rendered JSON vs. a signed async API** — is the whole game. It tells you in one minute whether "render the page in a browser" will take you an afternoon or a month. ## Why the headless browser hit a wall on Goofish The honest reality: **an open-source, self-hosted browser is not a stealth tool.** `browserless` (the container I deployed) is a *generic* renderer. Its free tier renders pages, executes JavaScript, takes screenshots, converts PDFs. The anti-detection features — real `stealth` mode, captcha solving, unblocked proxies — are explicitly the **paid Cloud/Enterprise** tier. The message in their docs is unambiguous. So I was asking a general-purpose renderer to beat a platform whose entire business depends on defeating automated clients. That's not a configuration bug; it's a mismatch between the tool and the adversary. To actually scrape Goofish I'd have to choose one of a few much heavier paths: 1. **Reverse the app's signing algorithm** and call the search API directly. This is how the serious scrapers do it — but the signature changes, so it's an ongoing maintenance cost, not a one-time build. 2. **Buy a third-party data API** (Alibaba Cloud's marketplace and similar sell `item_search` endpoints). Clean data, but you pay per call and you're at the mercy of the vendor. 3. **Drive a real, logged-in browser at low frequency** and hope the rate stays under the radar. Works for occasional manual checks, useless for reliable monitoring at scale. None of these are "spin up a container and go." That's the point. ## The reusable checklist Before I touch Playwright again on a new target, I now answer three questions in order: 1. **Where does the data actually live?** Open the page with JavaScript disabled (or read the raw HTML). If the data is in the HTML, I don't need a browser at all — I need an HTTP client and a parser. The browser is waste. 2. **If it's not in the HTML, what does the async request look like?** Open DevTools → Network, find the XHR/fetch that returns the data, and read its headers and query params. Is there a signature? Is it stable, or does it change per request? 3. **Is there a signed API?** If yes, stop. Decide up front whether the target is worth either (a) reverse-engineering the sign, or (b) paying a data vendor. If neither, the honest answer is "this costs more than it's worth" — and *knowing* that in the first hour is itself a win. Most scraping guides skip all three and jump straight to "install Puppeteer." That's the expensive path, the one that burns a day discovering the hard way what a 60-second investigation would have told you. ## What I'd do differently - **Disable JavaScript before reaching for a browser.** It's the single fastest signal for "server-rendered vs. client-fetched." Ten seconds, no setup. - **Don't buy stealth from a generic renderer.** Stealth is either a product you pay for (browserless Cloud, ScrapingBee, etc.) or something you build and maintain. It is not a checkbox in an open-source container. - **Distinguish "rendering" from "extraction."** A headless browser returns HTML; it does not hand you structured product fields. Even when rendering works, someone still has to write the selectors or the `evaluate` script. That's a separate task — plan for it, don't assume it's free. ## The result The contrast sharpened into a rule I now apply to every project: **decide whether the target is server-rendered or API-gated before choosing a tool.** On Carousell (server-rendered), a plain HTTP parser has archived thousands of listings with prices and photos for months. On Goofish (signed API), I stopped after an honest look at the effort and the maintenance burden — which is itself the right outcome for a project where a manual search once in a while does everything I needed. A headless browser is the right tool *sometimes* — for screenshots, PDFs, taking over a human session, or targets that render server-side. It is not a universal scraping key, and treating it as one is the expensive mistake. --- ## Want me to look at a site before you commit to scraping it? Most scraping projects fail in the first hour because nobody checked whether the target even *lets* data be scraped cheaply. I do that check before writing any code — and I build the monitors, notification pipelines, and self-hosted infrastructure that actually run. If you're eyeing a data source and want to know if it's an afternoon or a month, let's talk: - 📱 **WhatsApp:** [+60 12-797 2969](https://wa.me/60127972969) - 📧 **Email:** [me@hoelee.com](mailto:me@hoelee.com) - 🌐 **Website:** [hoelee.com](https://hoelee.com) ```` --- ## 文章 05 / 11:一个字符让我的 authentik CSS 静默失效 - **Slug**: `authentik-css-greater-than-bug` - **英文标题**: The Character That Silently Broke My authentik CSS - **分类**: `devops` | **发布日期**: 2026-09-20 - **线上地址**: https://blog.hoelee.com/posts/authentik-css-greater-than-bug/ (EN) | https://blog.hoelee.com/posts/zh/authentik-css-greater-than-bug/ (ZH) - **本篇审核状态**: ⏳ 待审核 (Pending) ### ✍️ 顾问审阅意见区 (Review Feedback) ```text 【审阅结论】:[ ] 通过 [ ] 需微调 [ ] 需重构 【事实与数字核对】: 【人设与用词卫生】: 【CRO与转化建议】: ``` ### 🇨🇳 中文版原文 (Chinese Markdown Twin) ````markdown --- title: "一个字符让我的 authentik CSS 静默失效" description: "我的 authentik 自定义 CSS 看起来完全正确,也匹配到了目标元素,却毫无效果。原因是一个 > 字符被 authentik 转义成了无效文本。" pubDate: 2026-09-20 updatedDate: 2026-10-16 category: devops tags: ["authentik", "css", "self-hosting", "debugging", "browser"] ogImage: /og/zh-authentik-css-greater-than-bug.jpg banner: /banners/authentik-css-greater-than-bug.jpg draft: false --- 我花了一个下午去查一条本该三十秒解决的 CSS 规则。 我想隐藏 authentik 登录页脚里的一行——硬编码的「Powered by authentik」署名。这条规则在我做过的其他项目里从来都是有效的: ```css ul.pf-c-list > li:last-child { display: none !important; } ``` 它毫无作用。不是「看起来有点不对」——那个元素依然完整地显示着。下面 是我追过的四个错误答案、唯一正确的答案,以及我本该第一个就做的调试 动作。 ## 为什么这不只是一行页脚的问题 如果你自托管 authentik,曾经把 CSS 粘进 **System → Brands → Custom CSS** 却完全没效果,那你多半以为自己哪里 写错了。你几乎肯定没写错。样式表被接收、被存储、被送到浏览器、也被 解析了——然后静默失效,你够得着的任何日志里都没有报错。 这是最糟糕的一类 bug:没有反馈回路。这篇文章把这条回路还给你。 ## 错误答案 #1:这是 shadow DOM,CSS 进不去 我的第一个假设。现代 Web Component 常常把标记藏在 shadow root 后面, 普通的文档 CSS 无法跨越那道边界。authentik 的登录页确实由 Web Component 渲染——我在页面源码里见过 `` 和 ``——所以这看起来显然是答案。 我从打包好的 bundle 里把组件定义读出来确认: ```js var oe = class extends L { createRenderRoot() { return this } render() { ... } } ``` `createRenderRoot(){ return this }` 意味着**没有 shadow root**——组件 直接渲染进 light DOM。普通 CSS 完全可以作用于它。 错误答案。继续。 ## 错误答案 #2:CSS 根本没被注入 下一个理论:我的 CSS 压根没进到页面里。我在返回的 HTML 里搜了一个 自己规则里的特征类名: ```