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.
2.2 KiB
ADR 0011: Runtime arg validation at the model boundary
Status: accepted (2026-06-13)
Context
defineTool (ADR 0005) gives tool authors a typed execute(args) via the InferArgs<S> mapping. But that type is a compile-time claim about a value that arrives at runtime as model-generated JSON: nothing forced the model to honor the schema, so a malformed call — missing a required key, a string where a number was declared, an enum value outside the set — reached execute typed-in-name-only. The tool body then either crashed on the bad shape (a generic stack trace the model can't act on) or, worse, silently misbehaved. Meanwhile the converter already encodes the exact structure a validator would need to walk.
Decision
validateArgs(spec, args): string[] interprets a SchemaSpec over a runtime value, returning human-readable violations (empty = valid), and is total (never throws). defineTool runs it before the typed body; on violations it throws ToolArgsError (code: 'INVALID_ARGS', message listing the violations), which the registry's existing execute-waterfall catch turns into an isError result the model reads and self-corrects from.
The validator mirrors schemaSpecToJsonSchema semantics exactly — same structure walked, same rules: top level must be a non-array object; required keys come only from required: true; extra keys are allowed (no additionalProperties: false); default is not applied; an object/array prop without properties/items only type-checks; enum is membership. Raw-registered (MCP) tools are not touched — they validate their own input.
Consequences
- The model gets actionable feedback on its own malformed calls instead of an opaque crash, closing the gap between
InferArgs's promise and runtime reality. - The validator and
InferArgsmust stay in agreement; that drift risk is closed by a property test (RFC 001) generating args that satisfyInferArgsand asserting they passvalidateArgs. ToolArgsErroris a plainErrorwith acodefield for now; if a harness-wide error taxonomy lands it becomes a subclass without changing callers that read.message.- Validation cost is negligible next to a model call.