ASK KNOX
beta
LESSON 356

Structured JSON: Why Output Shape Matters

The shape of your output determines what's possible downstream — get this wrong and your entire pipeline has to be rebuilt around the mess.

9 min read·Browser Agents & the Conductor Stack

Introduction

Two browser skills. Both extract articles from the same blog. One returns this:

The blog has several recent posts. There's one about machine learning from last Tuesday, 
another covering API design published on the 22nd, and a third piece on team culture 
from two weeks ago. The ML post seems to be the most popular based on the comment count.

The other returns this:

{
  "source": "example-blog.com",
  "extracted_at": "2026-05-30T08:00:00Z",
  "status": "success",
  "articles": [
    {
      "title": "Machine Learning in Production",
      "url": "https://example-blog.com/ml-production",
      "published_date": "2026-05-27",
      "comment_count": 47
    },
    {
      "title": "API Design Principles We've Learned the Hard Way",
      "url": "https://example-blog.com/api-design",
      "published_date": "2026-05-22",
      "comment_count": 23
    },
    {
      "title": "Engineering Culture at Scale",
      "url": "https://example-blog.com/culture-scale",
      "published_date": "2026-05-16",
      "comment_count": 15
    }
  ]
}

Which one can you build on? The second one. Immediately, without any additional processing. You can sort it. Filter it. Store each row to a database. Pass articles[0].url to the next browser skill. Count how many articles were published this week. The first one requires another AI call just to get you back to where the second one starts.

Output shape is not a detail. It's the .

Core Concept

The Contract Idea

Think of a browser skill's output schema as a between two parties: the skill (which produces data) and every downstream step (which consumes it).

A contract specifies what will be delivered, in what format, with what guarantees. When both sides of a pipeline speak the same contract, you can change either side independently as long as the contract is preserved. When there's no contract — when output is "whatever the skill returns" — changes on either side break the other.

This is exactly why messy text output is dangerous for automation pipelines. Text has no contract. "The blog has several recent posts" is a sentence that a human can interpret. It is not something your orchestration layer can route to a database, compare against last week's output, or filter by date without first spending tokens and time having an AI parse it into something structured.

The cost of unstructured output compounds with every additional step in your pipeline.

Anatomy of a Good Output Schema

A well-designed skill output has two parts: a and a data payload.

The metadata envelope answers operational questions:

{
  "source": "example-blog.com",
  "extracted_at": "2026-05-30T08:00:00Z",
  "status": "success",
  "result_count": 3
}
  • source — where did this data come from? Required for multi-source pipelines.
  • extracted_at — when was this extracted? Required for freshness checks and deduplication.
  • status — did the extraction succeed? Required for error handling in the orchestration layer.
  • result_count — how many items were returned? Useful for detecting unexpected truncation.

The data payload is the actual extracted content — an array of consistently structured objects:

{
  "articles": [
    {
      "title": "string — the post title",
      "url": "string — the canonical URL",
      "published_date": "YYYY-MM-DD",
      "excerpt": "string — first 200 chars of body text"
    }
  ]
}

Every field has a consistent type. Every object in the array has the same fields. Date formats are ISO 8601, not "last Tuesday." URLs are absolute, not relative.

This consistency is what makes the output composable.

Schema-First Design

The common beginner mistake is to extract first and define the schema later. The correct order is reversed: define the schema first, then build the extraction logic to produce it.

Ask these questions before you write a single line of extraction code:

  1. What downstream step is consuming this data?
  2. What fields does it specifically need?
  3. What types do those fields need to be?
  4. What happens when a field is missing or the extraction fails?

Answering these forces you to think about the pipeline as a system rather than as a collection of independent scripts. A field that seems optional ("I'll maybe need the author name") is almost never just optional — either it's needed or it's dead weight adding noise to every downstream step.

Practical Application

Here's a before-and-after showing schema-first design in action.

Scenario: You want to track pricing changes for a competitor's product tiers.

Before (no schema design): You write an extractor that returns a string like "The competitor has three plans: Starter at $29/month, Pro at $99/month, and Enterprise which is custom pricing." This is fine for reading. It's useless for detecting that the Starter plan changed from $29 to $39 without writing another parsing step.

After (schema-first design): You define the schema first:

{
  "source": "competitor.com/pricing",
  "extracted_at": "2026-05-30T09:00:00Z",
  "status": "success",
  "plans": [
    {
      "name": "Starter",
      "monthly_price_usd": 29,
      "billing_options": ["monthly", "annual"],
      "is_custom_pricing": false
    },
    {
      "name": "Pro",
      "monthly_price_usd": 99,
      "billing_options": ["monthly", "annual"],
      "is_custom_pricing": false
    },
    {
      "name": "Enterprise",
      "monthly_price_usd": null,
      "billing_options": [],
      "is_custom_pricing": true
    }
  ]
}

Now your orchestration layer can compare plans[0].monthly_price_usd against last week's extracted value and fire a Discord alert when it changes. No additional AI step. No parsing. Just a numeric comparison.

Common Mistakes

Returning everything and filtering later. If the page has 50 data points and you return all 50, downstream steps have to figure out which 5 they need. Extract only what your pipeline actually uses. Extra fields add noise and slow debugging.

Inconsistent date formats. "May 30" vs "2026-05-30" vs "3 days ago" in the same field across different extraction runs makes date comparison impossible. Always normalize to ISO 8601.

Missing the error case. What does the schema look like when the page is behind a login wall, or the site is down, or the data structure is not present? Your orchestration layer needs to know. A status: "error" with an error_message field lets downstream logic handle failures gracefully instead of crashing on unexpected input.

Nested objects where flat arrays would work. Deep nesting makes iteration harder. If you're tempted to nest objects more than 2 levels deep, question whether the data is genuinely hierarchical or whether a flat array with a parent_id field would serve better.

Summary

  • Structured JSON output is a contract between skills and the downstream steps that consume them
  • A good schema has a metadata envelope (source, timestamp, status) and a typed data payload (consistent fields, consistent types)
  • Design the schema first, before writing extraction logic — forces you to think about what downstream steps actually need
  • Normalize everything: ISO 8601 dates, absolute URLs, numeric prices (not strings)
  • Always handle the error case explicitly — status: "error" is as important as status: "success"

What's Next

Your skill produces clean JSON. Now where does the browser actually run? In the next lesson, we'll cover the local-versus-cloud browser question — running a headless browser on your own machine versus using a cloud browser service — and the tradeoffs that determine which one belongs in your stack.