Prefer maintained dependencies over hand-rolling
优先选用持续维护的依赖,而非手写实现
The harness hand-rolls a lot of infrastructure that mature external packages already provide. Some of that is deliberate — vendored Cordis ([vendoring decision](2026-06-11-vendor-cordis-as-source.md)), the [twin LLM adapters](../architecture/2026-06-13-twin-llm-adapters.md), schemastery as the config-schema standard — but much of it accreted from an unstated "avoid new dependencies" reflex: the repo-wide external dep
English
Problem
The harness hand-rolls a lot of infrastructure that mature external packages already provide. Some of that is deliberate — vendored Cordis (vendoring decision), the twin LLM adapters, schemastery as the config-schema standard — but much of it accreted from an unstated "avoid new dependencies" reflex: the repo-wide external dependency list stayed tiny while packages grew their own SSE parsers, protocol framers, retry loops, and glob matchers. Nothing in AGENTS.md actually stated a dependency policy, so agents inferred one from the existing pattern, and the inferred rule ("don't add deps") is stricter than anyone decided. That is the "Not Invented Here" fallacy operating by default: every hand-rolled clone of a well-maintained library is code we test, document, review, and debug ourselves, with none of the ecosystem's accumulated edge-case fixes.
Decision
Introducing an external dependency is a legitimate simplification, not a policy exception. When a well-maintained package (or a Node builtin at our engine floor) covers a hand-rolled surface, replacing the hand-rolled code is the preferred direction, subject to the same evidence standard as any other simplification: the swap must genuinely shrink what we own — code, tests, and contract surface — rather than merely relocate complexity behind a wrapper.
The bar for a new dependency:
- Net deletion. The dependency replaces real owned code (implementation + dedicated tests + docs), not hypothetical future code. A dep that only adds capability is a feature decision, not a simplification.
- Health. Actively maintained, widely used, sensible transitive footprint. A tiny unmaintained package trades our code for someone's abandoned code.
- Fit at the boundary. The package's semantics cover our actual contract; residual semantics we still hand-roll around it count against the swap.
- Not a settled seam. schemastery (config schemas), vendored Cordis, the
@earendil-workstwins, and other decisions recorded in implemented Agent Notes are not reopened by this policy; a swap that collapses a recorded design needs to beat the recorded rationale, not just cite this note.
packages/util/'s "zero-dependency" charter describes that group's export discipline — util packages stay free of harness dependencies so any group can depend on them — and does not ban external packages where they simplify; a util package whose entire job a maintained external package does better should be replaced by the dependency, not preserved for the charter.
Dependency-swap proposals are recorded as proposed/simplification Agent Notes like any other removal, with the candidate package, the deletable surface, residual semantics, and supply-chain considerations stated. The supply-chain proposal owns advisory scanning and update cadence for the dependency list this policy grows.
Alternatives considered
- Keep the implicit no-new-deps culture. Rejected: it was never a recorded decision, and its cost is concrete — hand-rolled protocol and parsing code duplicates battle-tested libraries, inflates the per-file coverage burden, and slows every reviewer who must re-derive edge cases the ecosystem already fixed.
- A hard allowlist of approved packages. Rejected: the repo is pre-release and the dependency set is small; a per-PR evidence bar (net deletion, health, fit) plus review keeps judgment where the context is, without a standing committee artifact that would itself need maintenance.
- Vendor every new dependency like Cordis. Rejected: vendoring is for packages we must patch or pin against upstream churn (vendoring decision); applying it broadly recreates the maintenance burden the dependency was meant to shed. Ordinary npm dependencies with lockfile pinning are the default.
Consequences
- Agents and contributors surveying for simplifications now treat "replace hand-rolled X with package Y" as in-scope output; dsh-find-simplifications carries the corresponding guidance.
- The dependency list will grow, and with it the supply-chain surface; the mitigations live in the supply-chain proposal, which this policy makes more urgent.
- Root
AGENTS.mdcarries the one-line rule; this note owns the rationale and the bar.
中文
问题
harness 手写了大量基础设施,而成熟的外部包早已提供同等能力。其中一部分是有意为之——以源码形式收录的 Cordis(引入 vendor 的决策)、孪生 LLM(大语言模型)适配器、作为配置 schema 标准的 schemastery——但相当大一部分是在一种未经言明的「避免新依赖」下意识作用下逐渐累积而成的:仓库级的外部依赖清单始终很小,各包却各自长出了自己的 SSE(Server-Sent Events)解析器、协议分帧器、重试循环和 glob 匹配器。AGENTS.md 其实从未写下任何依赖政策,agent(智能体)只能从既有模式中自行推断出一条,而这条推断出的规则(「不要加依赖」)比任何人实际决定过的都更严格。这正是 Not Invented Here(非我发明)谬误在默认状态下运作:每一个对维护良好的库的手写克隆,都是要由我们自己测试、撰写文档、评审和调试的代码,却享受不到生态累积下来的边界情况修复。
决策
引入外部依赖是一种正当的简化,而不是政策特例。当一个维护良好的包(或我们引擎下限即已提供的 Node 内置能力)覆盖了某块手写接口面时,替换手写代码就是优先方向,并遵循与其他任何简化相同的证据标准:这次替换必须切实缩减我们负责维护的内容(代码、测试和约定面),而不是仅仅把复杂度挪到一个包装层后面。
新依赖的准入门槛:
- 净删除。 该依赖替换的是实际由我们维护的代码(实现 + 专属测试 + 文档),而不是假想中的未来代码。只增加能力的依赖属于功能决策,不属于简化。
- 健康度。 持续维护、广泛使用、传递依赖足迹合理。一个无人维护的小包,只是拿我们的代码换来别人废弃的代码。
- 边界契合。 该包的语义要覆盖我们的实际约定;仍需围绕它手写补齐的残留语义,要计入这次替换的减分项。
- 不触碰已定案的 seam。 schemastery(配置 schema)、源码收录的 Cordis、
@earendil-works孪生适配器,以及其他记录在已实现 Agent Note 中的决策,不因本政策而重开;一次会瓦解已记录设计的替换,必须胜过所记录的论证理由,而不能只援引本 Agent Note。
packages/util/ 的「零依赖」章程描述的是该分组的导出纪律(util 包不携带 harness 依赖,从而任何分组都能依赖它们),并不禁止在能带来简化时使用外部包;如果一个 util 包的全部职责有维护良好的外部包做得更好,就应当用该依赖替换它,而不是为了章程而保留它。
依赖替换提案与其他任何移除类提案一样,记录为 proposed/simplification Agent Note,写明候选包、可删除的接口面、残留语义和供应链考量。本政策会使依赖清单增长,这份清单的安全公告扫描与更新节奏由供应链提案负责。
曾考虑的替代方案
- 维持隐性的「不加新依赖」文化。 不予采纳:它从来不是一项有记录的决策,而其成本是具体的——手写的协议与解析代码重复实现了久经实战检验的库,推高了按文件计的覆盖率负担,还拖慢每一位评审人:他们必须重新推导生态早已修复的边界情况。
- 一份获批包的硬性白名单。 不予采纳:仓库处于预发布阶段,依赖集合很小;按 PR(Pull Request)设置证据门槛(净删除、健康度、契合度)再加评审,就能把判断留在上下文所在之处,无需一份本身也需要维护的常设委员会式产物。
- 像 Cordis 一样把每个新依赖都以源码形式收录。 不予采纳:源码收录(vendor)只适用于我们必须打补丁、或必须锁定以抵御上游变动的包(引入 vendor 的决策);将其推广到所有依赖,会重新制造出引入依赖本要卸下的维护负担。默认做法是普通 NPM 依赖加 lockfile 锁定。
后果
- 巡查简化机会的 agent 与贡献者,现在把「用包 Y 替换手写的 X」视为范围内的产出;dsh-find-simplifications 承载相应指引。
- 依赖清单会增长,供应链接触面随之扩大;缓解措施记录在供应链提案中,本政策使该提案更加紧迫。
- 根
AGENTS.md承载一行规则;论证理由与准入门槛由本 Agent Note 持有。