Files
deepseek-harness/docs/rfc/005-runtime-validation-and-error-taxonomy.md
T
Tianyi Cui 36a30180b8 feat(tools): validate model-generated tool args at the boundary (RFC 005 pt 1)
defineTool now runs validateArgs against the SchemaSpec before execute, so a
malformed model call returns a self-correctable isError result listing the
violations instead of reaching the typed body untyped-in-practice. The
validator mirrors schemaSpecToJsonSchema semantics exactly (required from
required:true only, extra keys allowed, default not applied, object/array
without properties/items only type-checks, enum membership).

tool-bash's hand-rolled type/required checks (carrying the TODO(RFC 005)
stopgap note) are slimmed to just the value constraints the DSL can't express
(non-empty strings, positive timeout). Graduates RFC 005 pt 1 to ADR 0011.
2026-06-13 23:00:42 +08:00

2.3 KiB

RFC 005: Runtime validation at the model boundary, error taxonomy, dev-mode invariants

Status: partially implemented — part 1 (arg validation) → ADR 0011; parts 2-3 in progress

Problem

Three gaps where compile-time guarantees stop:

  1. Tool args are model-generated JSON — defineTool's InferArgs<S> claim is only as true as the model's output. Today a malformed call reaches execute untyped-in-practice.
  2. Tool errors flatten to a text block; name/code/stack are lost, so future sandbox/retry plugins can't distinguish ENOENT from EACCES, and the model gets less actionable feedback than it could.
  3. Loop ordering invariants (seq monotonicity, step/turn event nesting, turn-number continuity) are asserted only where tests look.

Proposal

  1. Schema validation in defineTool: before execute, validate parsed args against the SchemaSpec (the converter already encodes the structure — a small interpreter walks it: presence of required keys, primitive type checks, enum membership, recursion into objects/arrays). On mismatch, return an isError ToolExecutionResult describing the violation — the model can self-correct. Raw-registered tools (MCP) keep validating their own input.
  2. Structured error taxonomy: per-package error classes extending a common HarnessError (name, code, cause chaining). ToolExecutionResult gains optional error: { name, code } alongside the model-facing text. The loop's errorData consumes it; session error events carry the code. This also properly fixes the non-Error-throw message degradation found in review.
  3. Dev-mode invariants: a dsh-invariants debug plugin (everything is a plugin — it's just listeners) asserting, when enabled: session seq strictly increases; step/start precedes its chunks; turn/start/turn/end pair and nest; tool/call has a matching tool/result; status transitions are legal. Enabled in tests and the demo; off in production. Doubles as executable documentation of the event contract.

Plan

2 first (taxonomy is a dependency of 1's error shape), then 1, then 3. Property tests (RFC 001) then close the loop: generated args ↔ validator ↔ InferArgs agreement.

Risks

Validator/InferArgs drift — covered by the RFC 001 composition property. Validation cost per call is negligible next to a model call.