Menu
Try in Playground

Zero JSON Diagnostics: Structured Compiler Errors for Agents

Zero's compiler emits machine-readable JSON diagnostics with stable error codes and structured repair plans. Here's the format, why it exists, and how an agent consumes it.

The Headline Feature

Zero's most distinctive feature isn't a piece of syntax. It's the way the compiler talks to whoever — or whatever — is reading its output.

Run a broken program through zero check --json and you get a feed an agent can read directly:

{
    "ok": false,
    "diagnostics": [
        {
            "code": "NAM003",
            "message": "unknown identifier",
            "line": 3,
            "repair": { "id": "declare-missing-symbol" }
        }
    ]
}

That's a small but loaded blob. Let's unpack each field and the design choices behind them.

The Anatomy of a Diagnostic

A diagnostic is a structured object that describes one problem in the source. The canonical fields:

  • code — a stable identifier (e.g. NAM003). Always means the same thing, regardless of compiler version.
  • message — a human-readable description. May change wording across versions; the meaning is pinned by code, not by the message.
  • line (and other location fields) — where the problem is.
  • repair — optional structured metadata describing a fix the compiler thinks would resolve the diagnostic. The shape of this field is itself documented and stable.

The top-level shape includes an ok boolean for the run as a whole and a diagnostics array; even when the run succeeds, the array might contain warnings or notes.

Stable Error Codes

The contract on code is the part most likely to surprise you. NAM003 today means "unknown identifier". NAM003 next month and next year will also mean "unknown identifier". Agents (and humans) can rely on that.

This matters because language models and tooling tend to cache or memorize what they've seen. If the meaning of NAM003 drifted with each release, every cached lookup would be unsafe. Pinning the code keeps:

  • Agent training data valid across versions.
  • Documentation indexable by code.
  • Tooling pipelines stable.

The human-readable message is free to change as the team improves wording. The code is the load-bearing identifier.

Repair Metadata

The repair field, when present, tells the consumer what kind of fix the compiler thinks would work:

{
    "code": "NAM003",
    "message": "unknown identifier",
    "line": 3,
    "repair": { "id": "declare-missing-symbol" }
}

declare-missing-symbol here is the kind of repair — the high-level intent. To get the actual edits, call zero fix --plan --json. That returns a plan that includes the file path, the byte ranges to modify, and the new text:

{
    "diagnostic": { "code": "NAM003", "line": 3 },
    "plan": {
        "id": "declare-missing-symbol",
        "edits": [
            { "kind": "insert", "line": 1, "text": "fun answer() -> i32 { return 42 }\n" }
        ]
    }
}

(The exact field names and shape may differ in your toolchain version — the principle is "structured data, not prose".)

An agent reading the plan has a few choices:

  1. Apply the edits as-is.
  2. Apply the edits with modifications.
  3. Reject the plan and reach for a different fix.

In every case, the agent is operating on structured data instead of trying to interpret an English suggestion. That's the difference between repair plans and the "did you mean ...?" hints in a typical compiler.

How an Agent Actually Uses This

A simplified end-to-end loop an agent might run:

  1. Generate or modify a Zero file.
  2. Run zero check --json against it.
  3. If the result is { "ok": true, ... }, move on.
  4. Otherwise, for each diagnostic:
    • Look up the code to understand what's wrong (with zero explain or a local table).
    • If a repair is offered and looks safe, call zero fix --plan --json for the edits.
    • Apply (or simulate) the edits.
  5. Go back to step 2.

Compare this to working off a prose message: the agent has to parse English, extract a likely line number, guess the kind of fix, and infer the exact insertion or replacement text. Each step is fuzzy. The JSON path replaces each step with a lookup against a documented schema.

Beyond Errors: Graph and Size

--json isn't only for diagnostics. Other commands expose structured data the same way:

  • zero graph --json — emits the dependency graph of a package as structured data. Useful for understanding what depends on what, and for agents that want to reason about call sites before touching them.
  • zero size --json — reports the on-disk size of compiled artifacts, broken down by target. The data is the same one humans see in zero size, just parseable.

These shapes are part of the design: any time the compiler has useful data, it's available as JSON for tools to consume without screen-scraping.

What the Schema Looks Like

Field names and exact shapes are documented in the Zero repository and will evolve while the project is pre-1.0. The categories you can expect:

  • Run metadataok, version, timing.
  • Diagnosticscode, message, location (file/line/column/byte ranges), severity (error/warning/note), optional repair.
  • Repair plans — when fetched, the structured edit list.

If you're building tooling against this surface, the safe pattern is to read the fields you know about, ignore unknown fields gracefully, and key off code for behavioral decisions.

A Quick End-to-End Walkthrough

Suppose your source uses an identifier that isn't declared:

pub fun main(world: World) -> Void raises {
    check world.out.write(answer())   // 'answer' is not defined anywhere
}

Running zero check --json produces something like:

{
    "ok": false,
    "diagnostics": [
        {
            "code": "NAM003",
            "message": "unknown identifier 'answer'",
            "line": 2,
            "column": 27,
            "repair": { "id": "declare-missing-symbol" }
        }
    ]
}

zero fix --plan --json returns the edit:

{
    "diagnostic": { "code": "NAM003", "line": 2 },
    "plan": {
        "id": "declare-missing-symbol",
        "edits": [
            { "kind": "insert", "line": 1, "text": "fun answer() -> i32 { return 42 }\n" }
        ]
    }
}

zero fix (without --plan) applies the edit in place, after which zero check returns ok: true. Each step is a discrete, inspectable transaction.

Why This Matters Beyond Agents

The same properties — structured output, stable codes, repair plans — also make life easier for:

  • Editors and IDEs. Squigglies and lightbulbs that act on repair IDs instead of parsed text.
  • CI pipelines. Failures that get logged with code for easy grepping and dashboarding.
  • Code-mod tools. Bulk fixes that target a code, not a regex over messages.

The system was designed with agents in mind, but human-facing tooling gets the same benefits.

Next: Agent-First Design

The diagnostic system is the most concrete example of Zero's agent-first philosophy. The next doc, agent-first design, zooms back out to the underlying principles — small surface, deterministic tooling, explicit effects — and how each of them earns its place.

Frequently Asked Questions

What are JSON diagnostics in Zero?

When you run zero check --json (or other commands with --json), the compiler emits its findings as structured JSON instead of human-formatted prose. Each diagnostic carries a stable error code like NAM003, the source location, a human-readable message, and — when applicable — a structured repair field describing how to fix it.

Why does Zero emit JSON instead of plain text?

Agents need to parse compiler output. Plain text diagnostics are written for humans and require regex over English to extract a line number or guess a fix. JSON is unambiguous: an agent reads the code field, looks up the documented meaning, and acts on the structured repair plan without ever parsing prose.

What is a stable error code in Zero?

Each diagnostic the compiler can emit has a short, stable identifier like NAM003 (unknown identifier). The contract is that the code keeps its meaning across compiler versions, even as the wording of the human message changes. Agents and tooling can pattern-match on the code without worrying about message drift.

What is a repair plan?

When the compiler thinks it knows how to fix a diagnostic, it attaches a structured repair field naming the kind of fix it would propose. Calling zero fix --plan --json returns the full plan — the edit operations to apply, the affected files and ranges. An agent can apply, modify, or reject the plan programmatically.

How does zero explain relate to JSON diagnostics?

zero explain <code> returns the human-readable explanation for a diagnostic code — what the error means, why the compiler raises it, and typical fixes. It's the prose side of the diagnostic. The code is stable, so cached explanations remain valid; agents fetch them when a code falls outside their training data.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED