上一篇《一个请求的一生》拆的是主流程——一切顺利时,一个请求怎么走完八站。但 agent 系统的大部分复杂度不在顺利的那条路上,而在「出事之后」:模型断流、进程崩溃、工具卡死、插件抛错。这一篇回答:DeepSeek Harness 凭什么敢说自己稳?
答案不是某个「稳定性开关」,而是一套悲观工程学:假设一切都会失败,然后给每种失败定价。我们把它拆成八道防线——断线重拨(重试的纪律:退避、抖动、白名单,连等待都先落账)、先落笔再动刀(调模型前、副作用前强制刷盘,失败就不动手)、结果未知(把「我不知道」连同核查指引一起喂给模型)、叫停之后(半句话存档、没做的盖章、重放走得通)、先礼后兵(deadline 看门狗、SIGTERM→SIGKILL、busy-time 计量)、隔离病房(插件抛错只降级,工具体异常翻译成模型可读的结果单)、限量供应(背压、并发池、spill,外加诚实的缺口清单)、复盘制度(七条血泪规则 + 四篇 postmortem + 100% 覆盖率门槛)。
下面这册动画书逐道拆解:每道防线解决什么事故、代码里对应哪个机制、为什么这样设计。医院比喻继续沿用——上一册讲这家医院怎么接诊,这一册讲它为什么很少出医疗事故。
看到最后你会得到一个可复用的判断:稳定的 agent 框架不靠更聪明的模型,靠的是出事之后「不丢现场」——每一次失败都留得下账、查得到因、补得上场。顺带一提,这套系统自己也有缺口(收件箱无界、webhook 不去重),拆解照实列出——知道哪里没防线,才知道下一道修在哪。
DEEPSEEK HARNESS · VOL.03 · STABILITY
从出事到结账 Where stability comes from:
from incident to invoice
会坏ASSUME
FAILURE
上一册拆的是这家医院顺利的一天:挂号、分诊、问诊、送检。这一册拆倒霉的一天——断流、崩溃、卡死、抛错,事故一样不落。它凭什么还稳?答案不是运气,是八道防线。 The previous book covered a smooth day: check-in, triage, consultation, orders. This one covers a bad day — dropped streams, crashes, stuck tools, thrown plugins. Why does the hospital still hold? Not luck: eight lines of defense.
00PROLOGUE · 倒霉的一天PROLOGUE · A BAD DAY
一切顺利的那天不算数The smooth day doesn't count
上一篇《一个请求的一生》讲的是顺利的一天:八站走完,皆大欢喜。但评价一家医院稳不稳,从来不是看顺利的那天——是看倒霉的那天:半夜停电、药房断货、手术做了一半监护仪黑屏。事故一定会来,所以真正的问题从来不是「会不会出事」,而是出事之后还剩下什么:剩得多,第二天照常开门;什么都不剩,就只能从头再来。这册动画书把「倒霉的一天」拆成八种事故、八道防线:外面的服务会坏(重拨),自己会崩(先落笔),坏到一半(结果未知),有人叫停(叫停之后),下属失控(先礼后兵),事故传染(隔离病房),家底耗尽(限量供应),同样的坑踩两次(复盘制度)。 The previous book covered a smooth day: eight stations, everyone happy. But you don't judge a hospital by its smooth day — you judge it by the bad one: a power cut at midnight, a pharmacy run dry, a monitor going black mid-operation. Accidents always come, so the real question is never "will something break" but what remains afterwards: much remains, the doors open again tomorrow; nothing remains, and you start over. This animated book breaks the bad day into eight classes of failure and eight lines of defense: outside services die (retry), the house itself crashes (write-ahead), things break halfway (unknown), someone calls it off (cancel), subordinates run wild (kill), accidents spread (isolate), resources run out (limits), and the same pit gets stepped in twice (review).
代码对照八道防线不是八份新代码,是散在各包里的既有机制:llm-retry(重试)、session-checkpoint-policy 与 repair(先写后做与崩溃修复)、guard/timeout-policy(看门狗)、subprocess-local 与 sandbox-local(隔离与强杀)、agent-loop 与 tools(结构化错误),以及 docs/defensive-patterns.md 和 docs/postmortem/(制度)。
In the harnessThe eight lines are not new code — they are mechanisms already spread across packages: llm-retry (retry), session-checkpoint-policy and repair (write-ahead and crash repair), guard/timeout-policy (watchdogs), subprocess-local and sandbox-local (isolation and kills), agent-loop and tools (structured errors), plus docs/defensive-patterns.md and docs/postmortem/ (institution).
朱批以为稳定靠「更严的测试」或「更聪明的模型」。模型不决定你崩不崩;崩溃之后账还在不在,才决定稳不稳。 Myththat stability comes from stricter tests or a smarter model. The model doesn't decide whether you crash; whether the ledger survives the crash decides whether you are stable.
01RETRY · 断线重拨RETRY · THE CALLBACK LADDER
外面的世界会坏,先给失败起名字The world breaks; name the failures first
问诊要对着录音机说话——模型服务随时可能卡带:断流、超时、限流。harness 的第一道功夫不是重试,是给失败起名字:每个失败都有编号,断流叫 TRANSPORT,超时叫 TIMEOUT,限流还会附上服务端要求的等待时长。有了名字才知道怎么对待:限流值得等一等再拨;密钥错了,拨一万次也没用。然后才是重试的纪律:等待一轮比一轮长(指数退避),每轮再掺一点随机抖动——不然一百个 agent 会在同一秒一起重拨,把刚喘过气来的服务再次打死;服务端说「两秒后再来」,就两秒整,不多等不少等。最讲究的是一笔:重拨之前,先把「要重拨」写进病历,再开始等——连等待本身都要先落账,崩溃重启之后,重拨次数才不会重复计数,也不会凭空清零。
The consultation is spoken into a tape recorder — the model service — which jams without warning: dropped streams, timeouts, rate limits. The harness's first craft is not retrying; it is naming failures: every failure carries a code — a dropped stream is TRANSPORT, a timeout is TIMEOUT, and a rate limit arrives with the wait the server asked for. A name tells you how to treat it: rate limits deserve another call later; a bad key won't improve over ten thousand calls. Then comes the discipline of retrying: waits grow round by round (exponential backoff), each round salted with a little randomness — or a hundred agents redial in the same second and flatten the recovering service again; when the server says "come back in two seconds", you come back in exactly two. And the finest stroke: before waiting, the harness writes "about to retry" into the chart — even waiting leaves a ledger entry — so after a crash the retry count is neither counted twice nor wiped.
代码对照失败结构化在 llm-deepseek adapter(传输失败 → TRANSPORT,带 providerRetryAfterMs);重试在 llm-retry 插件:默认 5 次、白名单 code 才重试、指数退避 + 对称抖动、尊重 retry-after;顺序是先 session.append('llm/retry'),再开始等。
In the harnessFailure structuring lives in the llm-deepseek adapter (transport failure → TRANSPORT code, with providerRetryAfterMs); retrying lives in the llm-retry plugin — five attempts by default, a whitelist of retryable codes, exponential backoff with symmetric jitter, honoring retry-after — and the order is fixed: append llm/retry first, then wait.
朱批以为重试就是「失败了再试一次」。重试是策略:哪些失败值得重试(白名单)、等多久(退避 + 抖动 + retry-after)、次数记在哪(病历)。三样缺一样,要么打死服务端,要么崩溃后失忆。 Myththat retry means "try again on failure". Retry is a policy — which failures deserve it (whitelist), how long to wait (backoff + jitter + retry-after), where the count lives (the chart). Miss one and you either flatten the server or lose count across a crash.
02WAL · 先落笔再动刀WAL · LEDGER BEFORE SCALPEL
自己也会坏:账写在动手之前The house itself breaks; the ledger goes first
医院自己也会停电:进程崩溃、断电、kill -9。防它的思想只有一句话:动手之前,先把要做什么写进病历。harness 把这句话焊死在两个关口——调模型之前,强制把日志刷盘;执行工具体之前,再刷一次。刷盘失败就不动手:宁可不做这台手术,不可没账地做。这叫 fail-closed——出故障时门关上,而不是敞开。那崩了之后呢?重启的病案员做修复:病历尾部写到一半被撕断的行,截掉半行;发现一轮就诊没写结诊记录,补一条「这轮被中断」的收尾;有悬着没回单的检查单,补一张回单——不留给医生一张悬空的单子。连写入本身都是原子的:先写临时文件、落盘、再落位,追加到一半失败的行回滚,绝不留下半行假数据。 The house itself loses power: the process crashes, the machine dies, something calls kill -9. The defense is one sentence: before acting, write down what you are about to do. The harness welds it shut at two gates — before each model call the log is force-flushed, and again before any tool body runs. If the flush fails, nothing proceeds: better to skip the operation than to perform it off the books. This is fail-closed — when something breaks, the door shuts instead of standing open. And after the crash? The record clerk repairs: a torn half-written line at the tail is cut off; a visit missing its closing entry gets a synthesized "this turn was interrupted" record; any order slip without a receipt gets one — no dangling slips left for the doctor. Even the writes are atomic: temp file, fsync, then link into place; a half-appended line rolls back, never leaving fake data behind.
代码对照session-checkpoint-policy 在 llm/stream 与 tools/execute 前强制 flush,失败 fail-closed;崩溃修复在 core/session 的 repair.ts——撕裂尾截断 + 合成 step/end、turn/end{interrupted} 与悬空 tool 的结果;JSONL 原子写 temp + fsync + link()。
In the harnesssession-checkpoint-policy force-flushes before llm/stream and tools/execute and fails closed on error; crash repair lives in core/session's repair.ts — torn-tail truncation plus synthesized step/end, turn/end{interrupted}, and results for dangling tool calls; the JSONL store writes atomically via temp file + fsync + link().
朱批以为崩溃恢复靠「定时快照」。dsh 没有快照——病历本身是只增的事件流,恢复 = 截掉撕坏的尾巴 + 补写收尾事件。日志即快照。 Myththat recovery needs periodic snapshots. dsh takes none — the chart is an append-only event stream, and recovery is "cut the torn tail, synthesize the closers". The log is the snapshot.
03UNKNOWN · 结果未知UNKNOWN · THE UNKNOWN OUTCOME
坏到一半,是最难交代的时刻Breaking halfway is the hardest moment to account for
最尴尬的事故不是失败,是不知道算不算失败:检查单已经送出去,手术做了一半停电——外部状态到底动没动?harness 的回答是把「不知道」当成一种正式结果,写清楚交给医生:回单上明明白白写着——结果未知;只读或幂等的操作可以重试;可能有副作用的,先核实外部状态或问用户,不要盲目重试。这句话救的是真金白银:把一笔「转账」盲目重试一遍,就是两笔转账。注意它的区分度:检查单还没登记开工的,是另一种结果——未开始,可以直接重试,没有歧义。系统拒绝把两种情况混成一句笼统的「失败了」。诚实分类比统一报错费事,但它让模型第一次拿到了做正确决策所需的信息——在 agent 系统里,错误不再是异常,是数据。 The awkward accident is not failure; it is not knowing whether it failed: the order slip is out, the operation was halfway through when the power died — did the outside world get touched or not? The harness answers by making "unknown" a first-class outcome, written out for the doctor: the receipt says plainly — outcome unknown; retry only if the operation is read-only or idempotent; if it may have side effects, verify external state or ask the user first, do not retry blindly. That sentence saves real money: blindly retrying a "transfer" is two transfers. Note the discrimination: a slip never registered as started is a different outcome — not started, safe to retry, no ambiguity. The system refuses to compress the two into one generic "failed". Honest classification costs more than uniform errors, but it hands the model, for the first time, the information a correct decision needs — in an agent system, an error is not an exception; it is data.
代码对照repair.ts 生成两种结构化结果,连同 isError 的 tool-result 一起喂回模型:TOOL_OUTCOME_UNKNOWN(附「先核查再重试」的原文指引)与 TOOL_NOT_STARTED(可直接重试)。
In the harnessrepair.ts synthesizes two structured results, fed back to the model with isError tool-results — TOOL_OUTCOME_UNKNOWN (carrying the "verify first, then retry" guidance verbatim) and TOOL_NOT_STARTED (safe to retry).
朱批以为把错误说得轻一点是对用户友好。在 agent 系统里,错误是要被「读」的——模型读到什么,决定它下一步做什么。撒谎的回单(假装成功,或一律说失败)会制造一串连锁的错误决策。 Myththat softening errors is user-friendly. In an agent system errors are read — what the model reads decides what it does next. A lying receipt (pretending success, or calling everything a failure) manufactures a chain of wrong decisions.
04CANCEL · 叫停之后CANCEL · AFTER THE CANCEL
取消不是消失,是另一种完整Cancelling is not vanishing; it is another kind of complete
病人中途说「不看了」。直觉做法是把现场清空,当什么都没发生。harness 反着来:取消之后,病历必须完整。医生已经说出的半截话,存档,并标注「被中断」——半句话也是话,下次问诊它是有效病史;已经喊出去但还没开工的检查单,补一张「未执行」的回单——病历里每张检查单都必须有回单,否则重放这段病史时会永远欠着一张。取消信号从顶层一层层传下去(AbortSignal 贯穿模型流、工具体、子进程),每一层收到后负责收拾好自己。为什么这么较真?因为「取消」最容易产生病历破洞:漏记半句话,重放对不上;漏记一张单子,重放永远欠账。在这套系统的账目里,每一次中断都要能对上账。 The patient says "I'm done" midway. The instinct is to wipe the scene as if nothing happened. The harness does the opposite: after a cancel, the chart must be complete. The half-sentence the doctor already spoke is archived, stamped "interrupted" — half a sentence is still a sentence, and valid history at the next visit; an order slip shouted but never started gets a "not performed" receipt — every slip in the chart must have its receipt, or replaying this history owes one forever. The cancel signal threads down layer by layer (an AbortSignal running through the model stream, tool bodies, subprocesses), and each layer tidies up after itself. Why so strict? Because cancellation is where chart holes are born: miss half a sentence and replay won't reconcile; miss a slip and replay is in debt forever. In this ledger, every interruption must balance.
代码对照agent.cancel() 清空 inbox 并 abort 活动相位;abort 时已收到的部分流以 interrupted:true 落 assistant/message;未启动的 tool call 合成 skipped 结果保 replay 有效;signal 一路传到 adapter、工具体与子进程。
In the harnessagent.cancel() drains the inbox and aborts the active phase; on abort, partial stream content already received is persisted as assistant/message with interrupted:true; tool calls that never started get synthesized skipped results so replay stays valid; the signal reaches the adapter, tool bodies, and subprocesses.
朱批以为取消后界面上「看起来干净」就是干净。真正的干净是账面干净:中断有标记、单据有回单、重放能走通。 Myththat a clean-looking UI after cancel means clean. Clean is on the ledger: interruptions marked, slips receipted, replay walkable.
05KILL · 先礼后兵KILL · TERMS, THEN FORCE
下属会失控,杀要杀得掉Subordinates run wild; the kill must land
下属有两类失控:磨蹭(工具卡住不动)和发疯(子进程死循环)。对磨蹭:每个工具有 deadline,超时转成结构化错误交给医生决定下一步,不吊着不管;模型流三百秒没吐字,也判超时。对发疯:手术在独立手术室里做——detached 进程组,主楼塌不了;清理是标准的先礼后兵:先敲门请退出(SIGTERM),宽限期内不走再动真格(SIGKILL),而且「动真格」是承诺不是请求——哪怕主楼先塌,强杀也要兑现。最难杀的一种下属:看起来在等(await),其实主线程在死循环。harness 的 worker 线程用「event-loop 忙碌时间」计费:挂起等待不算干活,真烧 CPU 的才算——所以同步死循环照样杀得掉。而这一切的前提是沙箱:平台上没有合格的隔离手段,就整个不执行——宁可拒绝这台手术,不可裸奔一台。 Subordinates fail in two ways: dawdling (a tool stuck) and madness (a subprocess spinning forever). For dawdling: every tool has a deadline — on timeout it becomes a structured error handed to the doctor, never left dangling; a model stream silent for 300 seconds counts as timed out too. For madness: the operation runs in its own theater — a detached process group, so the main building cannot fall; cleanup is terms-then-force: first a knock asking it to leave (SIGTERM), and if the grace period passes, the real thing (SIGKILL) — and "the real thing" is a commitment, not a request: even if the house falls first, the kill is honored. The hardest subordinate to kill looks like it's waiting (an await) while the main thread spins. The harness's worker threads are metered on event-loop busy time: suspended waiting counts as no work; only genuinely burning CPU counts — so a synchronous spin loop dies like anything else. And all of it rests on the sandbox: if the platform offers no qualified confinement, nothing runs — better to refuse the operation than to run it unconfined.
代码对照guard/timeout-policy 给 tools/execute 挂 deadline → TOOL_TIMEOUT;adapter 流空闲看门狗默认 300s;subprocess-local:detached 进程组 + SIGTERM→SIGKILL 升级(grace timer 特意不清除);code-runtime-worker-thread 计量 event-loop busy-time;sandbox-local 的 bwrap→Landlock→Seatbelt 链不可用即 fail-closed。
In the harnessguard/timeout-policy hangs a deadline on tools/execute → TOOL_TIMEOUT; the adapter's stream idle watchdog defaults to 300s; subprocess-local runs a detached process group with SIGTERM→SIGKILL escalation (the grace timer is deliberately never cleared); code-runtime-worker-thread meters event-loop busy time; sandbox-local's bwrap→Landlock→Seatbelt chain fails closed when unavailable.
朱批以为 SIGKILL 就是兜底。强杀会留下半完成的副作用——所以才需要第 03 站:强杀之后补一张「结果未知」的回单。杀是手段,对账才是收尾。 Myththat SIGKILL is the safety net. A kill leaves half-finished side effects behind — which is exactly why line 03 exists: after the kill, an "outcome unknown" receipt. The kill is the means; reconciliation is the end.
06ISOLATE · 隔离病房ISOLATE · THE ISOLATION WARD
一个科室失火,不该烧掉全院One ward on fire must not burn the hospital
医院里有很多科室(插件)。一个科室的会诊医生失联、抛异常,医院不能跟着停摆。harness 的做法是逐个隔离:旁听记录的观察者抛错,只记一条警告,主流程照走;单个插件加载失败,宿主照常起来。最关键的一步发生在错误的第一现场:工具体抛异常,异常不冒泡,而是被转译成一张 isError 的结果单,喂回给医生看。医生读到「文件不存在」,自己决定改路径还是换方案——错误处理从「终止路径」变成了「上下文的一部分」。这是 agent 框架与传统软件最大的分岔。错误也分级保真:模型侧的错误保留原始信息(哪家供应商、什么错),其余杂错压平成 UNKNOWN——重要的保真,次要的诚实降级,而不是一律堆给模型,或一律吞掉。
The hospital has many wards (plugins). When one consultant drops out or throws, the hospital must not shut down with them. The harness isolates one by one: an observing listener that throws earns only a warning line, and the main flow walks on; a single plugin failing to load leaves the host standing. The crucial step happens at the error's first scene: a tool body throws, the exception does not bubble — it is translated into an isError result slip, fed back for the doctor to read. Reading "file not found", the doctor decides whether to fix the path or change the plan — error handling shifts from a terminating path to part of the context. This is the deepest fork between agent frameworks and classical software. Errors are also kept with graded fidelity: model-side errors keep their original detail (which provider, what fault), while miscellaneous errors are flattened to UNKNOWN — what matters stays true, the rest degrades honestly, instead of everything being dumped on the model or swallowed wholesale.
代码对照session 观察者逐个 try/catch(invokeContainedSessionObservers,throw/reject 只记 warn);工具体异常 → isError 结构化结果(tools);子 agent 失败映射为 error/aborted/refusal,不传染父级,未发布前可整体回滚;agent-loop 里 LlmError 保真、其余压平 UNKNOWN。
In the harnessSession observers are contained one by one (invokeContainedSessionObservers — a throw or reject earns a warn, nothing more); tool-body exceptions become isError structured results (tools); a subagent's failure maps to an error/aborted/refusal stopReason without infecting the parent, and unpublished creations roll back wholesale; in the agent loop, LlmError stays faithful while the rest flattens to UNKNOWN.
朱批以为「隔离」就是把 try/catch 撒得到处都是。隔离是职责划分:谁的第一现场谁翻译,翻译完继续走流程;乱撒 try/catch 只会把错误翻译成谎话。 Myththat isolation means scattering try/catch everywhere. Isolation is a division of labor: whoever owns the first scene does the translating, then the flow goes on; try/catch scattered carelessly only translates errors into lies.
07LIMITS · 限量供应LIMITS · MEASURED SUPPLY
家底有限,谁都不许掏空The pantry is finite; nobody empties it
稳定还有个朴素的敌人:耗尽。慢消费者把内存撑爆、一个并发任务把队列挤满、一个超大结果把上下文顶爆。harness 的回答是处处限量:HTTP 流的写缓冲满了就等下游 drain,不无限堆积;工具并发池有上限,排队池满了给医生的是「可行动的错误提示」——不是拒绝,是告诉它怎么调整;超大结果落盘存档(spill),内存里只留尾部窗口。这一章也要诚实:有些地方没设防。收件箱(inbox)没有上界,堆积靠 turn 边界自然消费;webhook 记录了投递 ID,但没有跨投递去重——重复投递会重复触发。拆解要拆全套:缺口也是拆解的一部分——知道哪里没防线,才知道下一道该修在哪。 Stability has one plain enemy: exhaustion. A slow consumer swells memory, one concurrent task packs the queue, one oversized result blows the context. The harness answers with limits everywhere: when the HTTP write buffer fills, it waits for the downstream to drain instead of piling up; the tool pool is bounded, and when it is full the doctor receives an actionable error — not a rejection, a suggestion; oversized results spill to disk, with only a tail window kept in memory. This chapter also owes honesty: some places stand unguarded. The inbox has no upper bound — backlog drains naturally at turn boundaries; the webhook logs delivery ids but never dedupes across deliveries — a repeat delivery fires twice. A teardown must be complete: the gaps are part of it — know where no wall stands, and you know where the next one goes.
代码对照http-bridge 背压(res.write 返回 false 就等 drain;请求体 300MB 上限 413);agent-loop 有界滚动并发池;jobs-local 每 owner 并发上限 + 可行动错误提示;spill 包把超大结果落盘、内存留尾部窗口;缺口:inbox 无界、webhook 无投递去重。
In the harnesshttp-bridge applies backpressure (res.write returning false waits for drain; a 300MB body cap answers 413); the agent loop runs a bounded rolling tool pool; jobs-local caps concurrency per owner with actionable errors; the spill package parks oversized results on disk keeping a tail window in memory; the gaps: an unbounded inbox, a webhook without delivery dedupe.
朱批以为「不设限」是灵活。无界的队列不是灵活,是把爆炸的时机从「可以预测」推迟到「不可预测」。 Myththat "no limits" means flexibility. An unbounded queue is not flexibility; it defers the explosion from a predictable moment to an unpredictable one.
08REVIEW · 复盘制度REVIEW · THE REVIEW INSTITUTION
前七道防事故,第八道防「同样的坑来两次」Seven walls stop accidents; the eighth stops repeats
前七道都是机制,第八道是制度。dsh 仓库里有一份 defensive-patterns.md——七条「血泪规则」,每条对应一类真实翻过车、或差点翻车的缺陷。比如「正交结果独立上报」:一个进程可能「超时了」同时「退出码 0」(它把超时信号吞了)——两个事实必须各自上报,不能把一个塞进另一个的分支,否则调用方会把被截断的运行读成干净成功。再比如「清理必须等到静默」:发完 kill 就返回会留下孤儿进程——清理要异步等孩子退场。翻车本身也存档:docs/postmortem/ 里有四篇复盘,第 0004 号的教训尤其有意思——不是系统坏了,是「归因」坏了:子进程正常失败(ripgrep 没搜到东西、退出码 1)被误判成沙箱故障。错误归因本身就是事故。配上每文件 100% 覆盖率门槛、无密钥的会话录制回放(崩溃恢复场景不依赖真实 key 也能重演),机制防事故,制度防同样的坑来两次。 The first seven walls are mechanisms; the eighth is an institution. The repo carries a defensive-patterns.md — seven hard-won rules, each matching a class of defect that really shipped or nearly shipped here. One rule: report orthogonal outcomes independently — a process can time out and still exit 0 (it trapped the signal); the two facts must surface separately, or a caller reads a cut-short run as a clean success. Another: disposal must reach quiescence — returning right after issuing a kill leaves orphans; cleanup awaits the children's exit. The accidents themselves are archived: four postmortems live in docs/postmortem/, and number 0004 is the most interesting — the system didn't break; the attribution broke: a child's perfectly normal failure (ripgrep finding nothing, exit 1) was misread as a sandbox fault. Misattribution is itself an accident. Add a per-file 100% coverage gate and keyless session replay (crash-recovery scenes reenact without a real key), and you have the full institution: mechanisms stop accidents; the institution stops repeats.
代码对照docs/defensive-patterns.md 七条规则(正交上报 / 双方守契约 / 异步态≠同步态 / dispose 到静默 / dispatcher 容错 / 不给不可信输出环境变量 / symlink 用 unlink);docs/postmortem/ 四篇;per-file 100% 覆盖率门槛;keyless 会话录制回放。
In the harnessdocs/defensive-patterns.md holds the seven rules (orthogonal reporting / contracts honored on both sides / async state ≠ sync state / dispose to quiescence / dispatcher containment / no ambient env for untrusted output / unlink link-shaped paths); four postmortems in docs/postmortem/; a per-file 100% coverage gate; keyless recorded-session replay.
朱批以为复盘是「写文档给领导看」。在 dsh,复盘的终点是规则和测试——defensive-patterns 是写代码前的强制阅读,postmortem 的修复带 keyless 场景锁死。制度不产生记忆,制度把记忆变成代码。 Myththat a postmortem is a document for the leadership. In dsh, a postmortem ends as a rule and a test — defensive-patterns is required reading before lifecycle code, and postmortem fixes land with keyless scenarios pinning them. An institution doesn't produce memory; it compiles memory into code.
八道防线,一本账Eight walls, one ledger
八道回头串一遍:外面会坏就重拨(记着数),自己会崩就先落笔,坏到一半就承认未知,有人叫停就补全账目,下属失控就先礼后兵,事故传染就隔离,家底有限就限量,坑踩过了就立规矩。每道防线接住一种事故—— Run the walls once more: the world breaks, so redial with a count; the house crashes, so the ledger goes first; things break halfway, so the unknown is admitted; someone calls it off, so the books are closed anyway; subordinates run wild, so terms come before force; accidents spread, so wards isolate; the pantry is finite, so supply is measured; pits get stepped in, so they become rules. Each wall takes one class of failure —
- 01名字:你的失败有结构化的名字吗——还是一句笼统的 exception?Names: do your failures carry structured codes — or one generic exception?
- 02计数:重试的次数与等待,持久化在哪?重启后会不会重复计数?Counts: where do retry counts and waits persist? Do they double-count across a restart?
- 03落笔:调模型之前、有副作用之前,日志强制落盘了吗?落盘失败还动手吗?Ledger: is the log force-flushed before model calls and side effects? Do you proceed when the flush fails?
- 04未知:半完成的副作用,给模型的回单写的是「未知 + 核查指引」,还是一句笼统的「失败」?Unknowns: for half-finished side effects, does the model's receipt say "unknown + verify first" — or just "failed"?
- 05叫停:取消之后,日志完整吗?每张发出的单子都有回单吗?Cancels: after a cancel, is the log complete? Does every issued slip have its receipt?
- 06强杀:失控的下级杀得掉吗?「等待挂起」和「真死循环」区分了吗?Kills: can a runaway subordinate actually die? Do you tell suspended waiting from a real spin loop?
- 07隔离:一个插件坏,会带走谁?错误有没有被翻译成模型的下一步输入?Isolation: who goes down when one plugin breaks? Are errors translated into the model's next input?
- 08复盘:上次翻车,变成规则和测试了吗?Reviews: did the last crash become a rule and a test?
DEEPSEEK HARNESS · STABILITYDEEPSEEK HARNESS · STABILITY