Status: implemented
English | 中文
Failures crossed seams as bare strings. A tool error flattened to a text block — name, code, and stack lost — so a future sandbox/retry plugin couldn't tell ENOENT from EACCES, and the model got less actionable feedback than it could. A non-Error throw degraded further: the loop wrapped it in new Error(String(x)), dropping any code. And LlmError was the only typed error in the system, with no shared base, so there was nothing for a consumer to instanceof against generically.
A single HarnessError extends Error base in dsh-llm (the leaf package every other imports — no new dependency edge): a stable code distinct from message, cause chaining via ErrorOptions, and name defaulting to the subclass. isHarnessError narrows at seams.
LlmErrorandToolArgsError(dsh-tools) extend it, keeping their existing codes.ToolExecutionResultgains optionalerror: { name, code }, populated in the registry's catch when the thrown value is aHarnessError. The agent loop forwards it onto thetool/resultsession event (which gained the same optional field), so the structured failure survives into the log for retry/sandbox plugins and replay. The model-facing text block is unchanged.- The loop's
toErrorwraps a non-Error throw in aHarnessError(code: 'UNKNOWN', original chained ascause) instead of a bareError, so even a bad throw carries a routable code into the sessionerrorevent (which already surfacedcode).
- Errors are machine-routable end-to-end: a plugin can branch on
error.coderather than substring-matching a message. - One base class is imported widely, but it lives in the package everyone already depends on, so the cost is a single import, not a new edge.
deriveMessagesdoes not surfaceerrorinto model history — the model still sees the text block; the structured field is for code and replay.- Argument validation retains its existing code and behavior; package-owned diagnostic invariants carry their stable code independently so the invariant registry does not import a product package. The shared base adds cross-seam routing metadata without changing model-facing text.