Grammar as contract¶
A Capy library is not just a parser definition — it's a machine- verified contract between the people writing source files and the systems consuming generated output. This page walks through the workflow it unlocks.
The pattern¶
User describes intent
│
▼
┌────────────────────────────────┐
│ Agent drafts lib.capy │
│ capy check confirms it parses │ ◀── Iterate until clean
└────────────────────────────────┘
│
▼
┌────────────────────────────────┐
│ Source: script.capy │ ◀── Now THE CONTRACT exists.
│ endpoint GET "/users" │ Frontend / consumers can
│ returns "User[]" │ build against it TODAY.
│ end │
└────────────────────────────────┘
│
┌────────┼────────┐
▼ ▼ ▼
OpenAPI TS client Markdown
YAML (.ts) docs
│ │ │
▼ ▼ ▼
Mock Frontend Wiki
server devs readers
The middle box — script.capy — is the contract. Everything
upstream (the library) and everything downstream (the consumers) can
evolve independently. The contract is the seam.
What "contract" means here, concretely¶
A grammar gives you four things that an English-language spec doesn't:
- Machine validation.
capy check lib.capyeither accepts the library or names exactly which rule was violated. There's no "the spec is ambiguous on this point." - Round-tripping. Same source + same library → byte-identical output. Always. Forever. (Verified by the golden snapshot tests committed alongside every sample.)
- Typed inputs.
arg capture name ServiceNameplustype ServiceName { pattern ... }means typos at the input boundary become errors at transpile time, not silent bugs in production. - Reviewable surface. Anyone reading the library learns the complete set of statements the source can contain. Spec drift is physically impossible.
The "build before tested" claim¶
Here's the part that's genuinely new: downstream consumers can start work the moment the grammar parses, before any library target is implemented.
Why? Because the grammar tells you:
- What entities exist (
endpoint,param,returnsin the API example). - What types those entities accept.
- What relationships are allowed (block nesting, ordering).
That's enough to:
- Write the frontend that will consume the future API.
- Mock the future generated output for testing.
- Plan database migrations against the schema-to-be.
- Have product/design conversations grounded in real shapes.
The library implementation (OpenAPI YAML, TypeScript client, etc.) can land after — and when it does, the contract guarantees the generated artifacts match what consumers were planning around.
The continually-tested loop¶
Every Capy sample includes a *.expected.txt golden file. The Go
test harness runs script.capy through every library and diffs
against the golden:
What this catches:
- A library author tweaks
lib_X.capyand accidentally changes the output shape for existing source. CI rejects. - Someone adds a new function pattern that overlaps with an existing one and breaks parser priority. CI rejects.
- A template helper changes meaning (e.g.
unquotestrips differently). CI rejects.
In other words: the library implementation can iterate aggressively without breaking the contract, because every change must survive the goldens.
The worked sample¶
Look at samples/contract-first-api/:
samples/contract-first-api/
├── script.capy ← THE CONTRACT
├── lib_openapi.capy ← Target 1: OpenAPI YAML
├── lib_typescript.capy ← Target 2: TS client stubs
├── lib_markdown.capy ← Target 3: API documentation
├── script_openapi.expected.txt
├── script_typescript.expected.txt
└── script_markdown.expected.txt
One source, three targets. The CI test (cmd/capy/contract_first_test.go)
runs all three libraries against script.capy on every commit. Add a
4th target (Postman, FastAPI server, Rust reqwest client) by
writing one new lib_<X>.capy + one new script_<X>.expected.txt.
A useful agent workflow¶
When the user asks for "a DSL for X" — REST APIs, infrastructure config, scene descriptions, anything declarative — drive this loop:
- Clarify the contract. "What entities? What relationships? What downstream consumers?"
- Sketch the source first. Write 3-5 lines of what
script.capyshould look like. Confirm with the user that this is the surface they want to author against. - Draft
lib.capy. Implement just enough to make the example parse. Validate withcapy_check. - Pick one target. Implement
file_templateto produce the first useful output (often Markdown or JSON — easy to inspect). - Capture a golden. Run the transpile, commit the output as
script.expected.txt. The contract is now fully closed. - Hand off. Consumers can build against the contract; library
authors can add targets without touching
script.capy.
The Capy MCP server has capy_check exactly for step 3 — it's the
fastest feedback loop the agent can run.
When this is the wrong tool¶
- The "source language" you want is fundamentally Turing-complete (you need full control flow, closures, IO). Capy is for declarative-shaped DSLs. Use a real PL.
- The output isn't structured (free-form prose, image generation). Capy is a transpiler; it needs a target with describable shape.
- You only have one consumer and one target forever. The contract-as-grammar overhead doesn't pay off until there are at least two consumers (or two targets) that need to stay in sync.
For everything else — APIs, configs, scenes, schemas, manifests, clients-in-N-languages — the grammar-as-contract pattern is what Capy was designed for.