Everyday Programmer
Work with Jimmy
AI Workflows

What Happens After Your AI Returns JSON?

A practical look at the boundary between a model response and application state: whole-plan validation, blocking clarifications, bounded correction, and recovery that still needs evidence.

7 min readstructured outputs, AI workflows, API design, validation, Helmweave
Glowing amber JSON fragments pass through a dark slate validation gate. An amber branch waits, while a teal route continues toward a separate, unlit verification gate.

An AI response becomes much easier to work with when it arrives as JSON. The application can inspect fields, render a useful interface, and decide which part of the workflow comes next.

The difficult part starts when that response is allowed to change something.

A plan can parse successfully and still contain a dependency that doesn't exist. A response can ask a blocking question while also requesting that work begin. A recovery decision can recommend another verification run without establishing that the work has passed.

In Helmweave, those cases sit at the boundary between a model proposing work and the application deciding what can happen. That boundary needs a contract with failure behavior, not just a shape for the happy path.

Be clear about what the provider guarantees

“Structured output” can refer to different mechanisms. JSON mode targets valid JSON. Schema-constrained output adds adherence to a supported schema. Tool calling represents a proposed function invocation and its arguments; the application still controls execution. The exact guarantees depend on the provider, model, endpoint, and configuration.

OpenAI's structured-output guide distinguishes JSON mode from schema adherence and documents refusal and incomplete-response handling. Those outcomes need explicit branches before the application treats a response as usable data.

Helmweave also has different paths. The orchestrator requests JSON-object output, while the generic API-worker path returns text that the planning host parses and validates. Describing the whole system as provider-enforced strict JSON Schema would overstate what the implementation does.

Whatever produced the response, the application still has to decide whether it makes sense in the current workflow.

Validate the relationships inside the response

Imagine a model proposing this small work plan:

json

This is an original, simplified example. The dependency fields have the expected shape. Both dependencies name existing tasks. The plan still has a cycle: neither task can start.

A field validator can check that dependsOn contains strings. The application needs another check to establish that the task graph is usable. Similar rules apply to a refund amount that exceeds the original payment, a booking whose end precedes its start, or a resource that belongs to a different tenant.

These are business invariants. Some can be represented in a schema; others require relationships, current records, or policy that the model cannot establish by naming them.

Helmweave's work-order validation checks the complete plan before it is installed. Among the rejected cases are unknown fields, duplicate task IDs, missing dependencies, dependency cycles, paths containing traversal or unsupported wildcards, and verification checks whose scope doesn't match the work.

The whole-plan boundary matters. Installing the first few tasks and discovering a broken dependency later leaves the application with a partially accepted plan. The safer behavior is to preserve the last valid state and explain why the proposed replacement was rejected.

For another application, I would start by writing down what must remain true after accepting a response. Then I would turn those statements into checks the application owns.

Give missing information a real state

A response contract should make it possible to say that work needs an answer before it can continue.

Here's an illustrative TypeScript shape:

ts

Plan stands for the application's separately defined plan contract. This type is a design sketch; runtime validation is still required for external data.

The useful decision is that the blocking-question branch has no executable plan attached. The interface can ask the question and keep the existing work intact. The user can see what is missing without wondering whether anything has already started.

Helmweave enforces a related rule in the host: when a clarification is blocking, assignment and control actions are suppressed. Optional questions are handled separately. That distinction needs to survive beyond the sentence shown in the conversation.

A reply saying “I need your answer before I continue” should have the same consequence in the state machine that it has for the person reading it.

Decide which failures deserve another attempt

An invalid response is a reason to classify the failure.

If the model omitted a required field, a bounded correction request may help. If the request lacks a decision only the user can make, repeating the prompt wastes time. If permission is missing, generating a more convincing proposal cannot supply it.

Helmweave's work-order correction loop allows three total attempts, including the first one. Validation feedback informs the correction attempts. If no valid work order is produced, the rejected proposal is not installed. Worker and access failures are handled outside this correction loop.

That limit makes the failure visible. It also gives the workflow a predictable stopping point instead of an open-ended loop that keeps spending time trying to produce something acceptable.

Normalization deserves the same care. A parser can deliberately support an outer code fence or a known footer without accepting arbitrary surrounding prose. Every accepted variation becomes part of the contract. Pulling the first JSON-looking object out of a long reply risks accepting something the model only meant as an example.

For a production workflow, I would also set a time budget and separate response correction from retries of side effects. Retrying generation and retrying a payment, invitation, or deployment are different operations. Before repeating an action, establish whether the earlier attempt took effect and use the service's duplicate-prevention mechanism where available.

Keep recovery separate from the result it requests

Suppose a recovery response recommends running verification again. The application can validate that recommendation and queue the checks. At that moment, verification is pending.

Helmweave makes that distinction explicit. Its recovery path can return work to development, request verification, request missing human input, or propose a plan amendment. A route back to verification resets the verdict to pending. The amendment path has its own validation and review gates.

Accepting the next step establishes what to do next. The eventual result still needs evidence from the checks that run.

This affects the frontend too. “Checking again” and “Verified” should be different states with different evidence behind them. A progress message can explain activity, but it shouldn't silently become a success signal.

The Making of Helmweave covers the wider workflow. Helmweave is experimental and under active development, so these are implementation decisions to inspect and test rather than claims about production reliability.

Authorize at the point of action

Even a sensible, well-formed proposal needs a permission check before it changes state.

The application should resolve the target from trusted records, check the current actor and scope, and confirm that the transition is still allowed. A model-supplied identifier, an approved flag, or an earlier UI state cannot substitute for that decision. Frontend Authorization Is Not Security describes the same enforcement boundary for ordinary product interfaces.

Freshness belongs here as well. A response can arrive after the user changed direction or after another worker updated the plan. Tie the proposal to the request and state version it was generated for. Recheck before applying it, so an older answer can't overwrite newer intent.

Those are design requirements for the integration. A JSON parser cannot enforce them on its own.

Test the paths that should leave state unchanged

The happy path is only one test. For a structured-response workflow, I would also cover:

  • A refusal or incomplete response reaches a visible, non-executing state.
  • Malformed data and unsupported fields are rejected before execution or installing the proposal.
  • A plan with duplicate IDs, missing dependencies, or a cycle is rejected as a whole.
  • A blocking clarification prevents actions even if the response also requests them.
  • Correction stops at the configured attempt and time limits.
  • Permission changes and stale responses are caught immediately before action.
  • A recovery request for verification produces a pending state, never a passing verdict.
  • Repeated delivery cannot accidentally repeat an already completed side effect.

For each test, check the state that remains as carefully as the error that appears. A helpful message doesn't compensate for work that already started when it should have waited.

Structured responses give the application something concrete to inspect. The engineering work is deciding what each accepted response allows, what evidence it requires, and what stays unchanged when it fails.

That's the contract I would design before connecting the model to the next action.

Keep reading

Related articles