The view written as a string
Asked for a view as a tool argument, the model wrote it as JSON inside a string, a third of the time, and sometimes with a brace missing. The shape of the schema decided it.
Date: 2026-11-10. Context: views in the chat (feat/genui-chat, journal ch. 44).
What happened
The first show_view took { view }, with view typed as "an object of anything"
(z.record(z.string(), z.unknown())). The first evaluation, five real questions to Claude Haiku
5.5, showed no view at all. The tool was never reached: every call failed the input's validation.
The model had sent {"view": "{\"type\":\"Stack\",…"}: the view as JSON text inside a string,
escaped by hand, 1,474 characters of it.
Giving it the spec's real schema, recursive, with one variant per component and $ref for the
children, did not change much. Telling the tool to parse a string did not save it either: one of
those strings ended in ]}}]}}, a brace short. A plain, non-recursive schema for view brought it
to two objects out of three. Then the view's root node became the tool's input itself, { type, props, children }, and the model sent an object four times out of four, and in the next three
evaluations every view arrived as an object.
What we learned
- A schema is an instruction about form, not only a check. "An object of anything" told the model nothing about the shape, and it fell back on the safest thing it could write: a string.
- JSON in a string is the worst of both. The model has to escape every quote by hand, and one slip makes the whole view unreadable. A string that cannot be parsed is not a near miss.
- The top level is the safe place. A tool's arguments are always an object; nested inside one, an argument may come back as text.
- A refusal at the door costs more than one inside. An input refused by its schema is retried by the model again and again, outside the tool's own count of tries. Every input now passes the schema, and the tool refuses a wrong one as a try.
What it means for Vektos
It is the benchmark's question in a new form: how an agent behaves depends on how the tool is described to it, not only on what the tool does. The design-system MCP's answers are Markdown for the same kind of reason (guide 07). A grader of generated UI should measure the share of tool calls that arrive malformed, per model and per schema shape: it moved from about 100% to 0% here without the model changing.
Follow-ups
- Keep
chat:eval-viewsas the check of any change to the tool or the prompt. - Try the recursive schema again with other models, through the provider seam, before deciding it is the schema and not the model.
Valid and empty
Two generated UI specs passed json-render's validation and showed nothing. The root pointed at an element that was never written, and the check that said so was a different function.
The 390 that was 1440
Eighty accessibility checks reported clean at 390 pixels. They had run at 1440: the resize did nothing to a tab in the background, and nothing said so.