Spec-First Prompting is a technique for building and refining AI-powered workflows where the specification of the desired behavior—what the AI should do and how—comes before any implementation or execution. It matters when the AI’s output must be consistent, predictable, and align closely with domain-specific expectations, such as in documentation generation, data transformation, API contract validation, or automated code review. The specification is not an afterthought but the source of truth for both human and machine.

Defining the Specification

A spec-first prompt begins with a clear, structured, and executable description of the expected input, output, behavior, and constraints. The specification is not a natural language description; it is a formal, machine-readable contract. It is typically written in JSON, YAML, or a domain-specific schema, and serves as the definitive guide for prompt engineering, model evaluation, and system validation.

The core artifact is a *prompt spec*, a file that defines:

  • input_schema: the expected structure of the input data
  • output_schema: the expected structure of the output
  • instructions: a list of steps or rules the AI must follow
  • constraints: conditions that must be satisfied
  • examples: one or more input-output pairs to ground the model’s understanding
{
  "input_schema": {
    "type": "object",
    "properties": {
      "title": { "type": "string" },
      "description": { "type": "string" },
      "tags": { "type": "array", "items": { "type": "string" } }
    },
    "required": ["title", "description"]
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "summary": { "type": "string", "maxLength": 200 },
      "keywords": { "type": "array", "items": { "type": "string" }, "minItems": 3 },
      "category": { "type": "string", "enum": ["product", "service", "event", "blog"] },
      "status": { "type": "string", "enum": ["draft", "review", "published"] }
    },
    "required": ["summary", "keywords", "category", "status"]
  },
  "instructions": [
    "Extract a concise summary from the description, limited to 200 characters.",
    "Generate at least three keywords relevant to the content and title.",
    "Assign a category based on the title and description using the provided list.",
    "Set the status based on the presence and completeness of the description and tags."
  ],
  "constraints": [
    "summary must be exactly 200 characters or less",
    "keywords must contain at least three items",
    "category must be one of: product, service, event, blog",
    "status must be draft, review, or published"
  ],
  "examples": [
    {
      "input": {
        "title": "AI-Powered Documentation Tool",
        "description": "A tool that generates documentation from code and user stories. It supports multiple formats and integrates with CI/CD pipelines.",
        "tags": ["documentation", "AI", "devtools"]
      },
      "output": {
        "summary": "AI-powered tool that generates documentation from code and user stories, supporting multiple formats and CI/CD integration.",
        "keywords": ["documentation", "AI", "devtools"],
        "category": "product",
        "status": "published"
      }
    }
  ]
}

Validating Prompts Against the Specification

Once a prompt is written, it must be validated against the spec. This is not a one-off task but a continuous process. A validation step checks whether a prompt’s output matches the expected schema, follows all instructions, and satisfies all constraints.

Validation is done using a standard tooling stack: curl, jq, jq, python, and jsonschema. The validation pipeline is typically composed of a script that:

  1. Loads the prompt spec
  2. Applies the prompt to a set of test inputs
  3. Compares the actual output against the expected schema and constraints
  4. Reports all mismatches

curl -s https://api.example.com/v1/prompt \
  -H "Content-Type: application/json" \
  -d @input.json \
  -o output.json

python3 -c '
import json, sys
from jsonschema import validate, ValidationError

with open("prompt-spec.json") as f:
    spec = json.load(f)

with open("output.json") as f:
    result = json.load(f)

try:
    validate(instance=result, schema=spec["output_schema"])
except ValidationError as e:
    print(f"Schema validation failed: {e.message}")
    sys.exit(1)

for constraint in spec["constraints"]:
    if constraint.startswith("summary must be exactly "):
        _, _, length = constraint.split(" ")
        if len(result["summary"]) != int(length):
            print(f"Summary length mismatch: expected {length}, got {len(result['summary'])}")
            sys.exit(1)

    if constraint.startswith("keywords must contain at least "):
        _, _, min_count = constraint.split(" ")
        if len(result["keywords"]) < int(min_count):
            print(f"Keywords count too low: expected at least {min_count}, got {len(result['keywords'])}")
            sys.exit(1)

    if constraint.startswith("category must be one of"):
        _, _, _, *allowed = constraint.split(" ")
        if result["category"] not in allowed:
            print(f"Category not in allowed list: {result['category']} not in {allowed}")
            sys.exit(1)
' || exit 1

Iterating on the Prompt Using the Specification

The real power of spec-first prompting lies in iteration. When a prompt fails validation, the failure is not just a list of errors—it is a direct path to refining the prompt or the spec.

A common failure is *schema drift*: the model generates a field not defined in the output_schema, but the consuming system expects a strict contract. The model adds author or last_updated, but the downstream system cannot parse the response.

Another failure is *instruction drift*: the model follows the instructions but misses subtle details. For example, the instruction says “generate at least three keywords,” but the model produces exactly two because it didn’t read the requirement carefully. The validation script detects this, and the spec is updated to include an explicit minItems: 3 in the keywords field.

When a new requirement emerges—say, “include a related_links array”—the team updates the spec:

"output_schema": {
  "properties": {
    "related_links": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "title": { "type": "string" }
        },
        "required": ["url", "title"]
      }
    }
  }
}

The next run of the validation pipeline fails because the model did not include related_links. The prompt is revised to explicitly ask for it, and the spec is now a living document.

Managing Multiple Prompts and Specifications

In a system with multiple prompts—each for a different task—the spec-first approach enables consistent, composable workflows. Prompts are versioned alongside their specs, and the specification becomes the source of truth for the entire AI pipeline.

Each prompt has its own prompt-spec.json, stored in a specs/ directory:

specs/
├── content-summarizer.json
├── code-reviewer.json
├── api-documenter.json
└── data-mapper.json

A CI/CD job runs on every commit:

# .github/workflows/validate-prompts.yml
name: Validate Prompts
on: [push]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Validate all prompts
        run: |
          for spec in specs/*.json; do
            echo "Validating $spec"
            python3 validate.py "$spec"
          done

The validate.py script uses jsonschema and jq to check each prompt against its spec and logs any discrepancies.

Common Failure Modes

The most silent failure is *spec drift*: the spec changes, but the prompts are not updated to reflect it. The model runs against an old spec, producing outputs that satisfy the previous version but fail against the new one. The validation pipeline catches this, but the fix requires manual coordination.

Another failure is *incorrect constraint evaluation*. A constraint like “status must be draft, review, or published” is often mistaken for “status must be one of the following: draft, review, published,” but the model outputs “Published” with a capital P, and the validation fails silently. The solution is to use caseInsensitive: true in the enum validation, or to normalize the status field in the validation script.

A frequent confusion is between required fields and constraints. A field may be marked as required in the schema, but the spec includes a separate constraints list that enforces additional rules. The model may produce a summary field, but the summary is not 200 characters long. The validation catches it, but the team must ensure that schema-level required checks are not duplicated in constraints.

Another sharp edge is *example-driven inference*. The model may perform well on the examples provided in the spec, but fail on unseen data. The validation pipeline includes a test-suite.json file with diverse inputs, and the job fails if any example fails. This forces the team to curate a robust set of test cases.

Enforcing Consistency Across Teams

When multiple engineers contribute prompts, the spec-first approach ensures consistency. A new team member writes a prompt for “generate changelog entries,” but the output format differs from the rest. The validation script fails, and the new prompt is rejected until it conforms to the spec.

To reduce friction, a standard prompt-template.yaml is provided:

# template.yaml
input_schema:
  type: object
  properties:
    content: { type: string }
    version: { type: string, pattern: '^\d+\.\d+\.\d+$' }
    changes:
      type: array
      items:
        type: object
        properties:
          type: { type: string, enum: [ 'feature', 'bugfix', 'documentation', 'performance' ] }
          description: { type: string }
        required: [ 'type', 'description' ]
    changelog:
      type: array
      items:
        type: object
        properties:
          version: { type: string }
          date: { type: string, format: 'date' }
          entries:
            type: array
            items:
              type: object
              properties:
                type: { type: string }
                description: { type: string }
              required: [ 'type', 'description' ]
        required: [ 'version', 'date', 'entries' ]
  required: [ 'content', 'version', 'changes' ]

output_schema:
  type: object
  properties:
    title: { type: string }
    entries: { $ref: '#/definitions/entry' }
    summary: { type: string, maxLength: 150 }
    version: { type: string }
  required: [ 'title', 'entries', 'summary', 'version' }

definitions:
  entry:
    type: object
    properties:
      type: { type: string, enum: [ 'feature', 'bugfix', 'documentation', 'performance' ] }
      description: { type: string }
    required: [ 'type', 'description' }

instructions:
  - Generate a changelog title based on the content and version
  - For each change, create an entry with type and description
  - Generate a summary of all changes, limited to 150 characters
  - Return the entire changelog as a list of entries

constraints:
  - "title must be at least 10 characters"
  - "entries must contain at least one item"
  - "summary must be exactly 150 characters or less"
  - "version must follow the pattern X.Y.Z"

New prompts are required to use this template and pass validation with a set of test cases.

Final Considerations

The spec-first approach is not a one-time setup. It requires discipline to maintain the spec files, version them, and keep them in sync with the prompts and the data. The most common mistake is letting the spec diverge from reality—the actual output of the AI system no longer matches the spec.

A successful implementation includes a docs/specs/ directory with a README.md that explains:

  • How to write a new spec
  • How to validate a prompt
  • How to update the spec
  • How to run the full validation suite

The validation pipeline is not just a CI job—it is a contract between the AI system and its consumers. Every failure is a reason to improve the specification, and every change to the specification triggers a new wave of prompt refinement.

In the end, the spec is not just a document; it is the code of the AI system.

This page was rewritten on 11 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.