Skip to Content
Vektos
Anecdotes

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-views as 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.

On this page