NTNT

← Back to Blog

Intent-Driven Development (IDD)

January 25, 2026 - NTNT Team

Intent-Driven Development (IDD) is a methodology where human requirements become executable specifications. You write what your software should do in natural language, the system verifies that your code actually does it.

At the heart of IDD is the Intent Assertion Language (IAL), a term rewriting engine that translates natural language assertions into executable tests.

More Than Acceptance Testing

IDD isn't just BDD or spec-driven development with different syntax. It spans from unit tests to integration tests to acceptance tests, all in one unified language. Here's a unit test example:

# math.intent

Feature: Math Utilities
  id: feature.math_utils
  description: "Core mathematical functions"

  Scenario: Calculate discount correctly
    When calculating discount
    → result matches expected

  Scenario: Discount is deterministic
    When calculating discount
    → discount is predictable

---

## Glossary Extensions (Unit Testing)

| Term | Means |
|------|-------|
| calculating discount | call: calculate_discount({price}, {percent}), source: lib/math.tnt, input: test_data.discount_examples |
| result matches expected | result is {expected} |
| discount is predictable | property: deterministic |

---

## Test Data

Test Cases: Discount Examples
  id: test_data.discount_examples

  | price | percent | expected |
  | 100 | 10 | 90 |
  | 50 | 25 | 37.5 |
  | 200 | 0 | 200 |

This tests a pure function directly, with multiple input/output pairs and property checks. The call: keyword invokes the function, input: specifies the test data table, and property: deterministic verifies the function always returns the same result for the same inputs.

The same .intent file can mix unit tests with HTTP scenarios:

  Scenario: Apply discount via API
    When POST /api/cart/discount with {"percent": 10}
    → status 200
    → body contains "total"

One file, one language, all levels of testing.

The Core Idea

Instead of writing requirements in a document and tests in code, you write both in one place: the .intent file. This file is both human-readable documentation and machine-executable specification.

# server.intent

## Glossary

| Term | Means |
|------|-------|
| page loads successfully | status 200, returns HTML |
| returns HTML | header "Content-Type" contains "text/html" |
| they see "{text}" | body contains "{text}" |

---

Feature: User Greeting
  id: feature.greeting
  description: "Display a personalized greeting"

  Scenario: Greet by name
    When a user visits /?name=Alice
    → page loads successfully
    → they see "Hello, Alice"

  Scenario: Default greeting
    When a user visits /
    → page loads successfully
    → they see "Hello, World"

The Glossary: Your Domain Language

The glossary is where the magic happens. You define terms in natural language, and IAL resolves them into executable checks:

| Term | Means |
|------|-------|
| page loads successfully | status 200, returns HTML |
| returns HTML | header "Content-Type" contains "text/html" |
| they see "{text}" | body contains "{text}" |

When you write → page loads successfully in a scenario, IAL expands it:

  1. page loads successfullystatus 200, returns HTML
  2. returns HTMLheader "Content-Type" contains "text/html"
  3. Finally resolves to primitives: Check(Equals, response.status, 200) and Check(Contains, response.headers.content-type, "text/html")

Terms can reference other terms, building a hierarchy from natural language down to executable checks. This is term rewriting, the core mechanism of IAL.

Built-in Standard Terms

IAL includes a vocabulary of standard terms you can use without defining them in your glossary:

Term Meaning
status 200 HTTP status equals 200
status 2xx Any success status (200-299)
body contains "{text}" Response body includes text
body not contains "{text}" Response body excludes text
header "{name}" contains "{value}" Header check
content-type is json JSON response shorthand
response time < 500ms Performance assertion
code is valid Passes lint checks

Your glossary terms extend and build upon these standard terms, creating a domain-specific language for your project.

Linking Code to Intent

NTNT links your implementation to your intent file using @implements annotations:

// server.tnt
import { html } from "std/http/server"

// @implements: feature.greeting
fn home(req) {
    let name = req.query_params["name"] ?? "World"
    return html("<h1>Hello, {name}!</h1>")
}

get("/", home)
listen(8080)

The annotation // @implements: feature.greeting tells NTNT that this function implements the feature.greeting requirement from your intent file.

The Verification Workflow

Run ntnt intent check server.tnt and NTNT:

  1. Finds server.intent (linked by filename)
  2. Starts your server
  3. Executes each scenario's HTTP requests
  4. Resolves assertions through glossary → standard terms → primitives
  5. Reports pass/fail for every check
$ ntnt intent check server.tnt

🎯 Intent Check: server.intent

Feature: User Greeting
  ✓ Greet by name (2 checks)
  ✓ Default greeting (2 checks)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ All scenarios passed (4 checks)

Intent Commands

NTNT provides several commands for working with intent files:

# Verify implementation matches intent
ntnt intent check server.tnt

# Show which features have implementations
ntnt intent coverage server.tnt

# Generate code scaffolding from intent
ntnt intent init server.intent -o server.tnt

# Visual development with live tests
ntnt intent studio server.intent

Intent Studio: Visual Development

For the best experience, use Intent Studio:

ntnt intent studio server.intent

This opens a visual interface at http://127.0.0.1:3001 that shows:

  • Health bar: Overall pass/fail percentage at a glance
  • Feature cards: Expandable views of each feature and its scenarios
  • Live test results: Pass/fail indicators update as you code
  • Resolution chains: See how assertions expand from glossary to primitives
  • Native hot-reload: Edit your code, changes apply on next request

Your app runs on port 8081 while Studio runs on port 3001. Edit your .tnt file, refresh your browser, and see tests re-run instantly.

IAL Primitives

At the bottom of every resolution chain are primitives, the actual executable operations:

  • Http: Execute HTTP request, capture response
  • Cli: Execute CLI command, capture output
  • CodeQuality: Run lint/validation checks
  • FunctionCall: Call an NTNT function (unit testing)
  • PropertyCheck: Verify function properties (deterministic, idempotent)
  • Check: Universal assertion (equals, contains, matches, etc.)

You never write primitives directly. You write natural language in your glossary and scenarios; IAL resolves them to primitives automatically.

Why This Matters

Traditional development has a gap between requirements and code. Requirements live in documents that become outdated. Tests verify behavior but don't explain intent. Code comments try to bridge the gap but are often ignored.

IDD closes this gap:

  • Requirements are executable: They're not just documentation, they're tests
  • Code is traceable: Every function links back to its requirement via @implements
  • Changes are verifiable: Modify code, run check, know if you broke anything
  • Natural language is preserved: Humans read "page loads successfully", machines execute the checks
  • AI agents have clear targets: Tell an agent to implement feature.greeting and it knows exactly what success looks like

Design by Contract Integration

IDD works alongside NTNT's contract system. While intent files define feature-level requirements, contracts define function-level specifications:

fn withdraw(amount: Int) -> Int
    requires amount > 0
    requires amount <= self.balance
    ensures result >= 0
{
    self.balance = self.balance - amount
    return self.balance
}

In HTTP routes, contract violations automatically return proper error responses: failed preconditions return 400 Bad Request, failed postconditions return 500 Internal Server Error.

Getting Started

Ready to try IDD? Here's the workflow:

  1. Create a .intent file describing what you want to build
  2. Define your domain language in the Glossary
  3. Write features with scenarios and assertions
  4. Generate scaffolding: ntnt intent init app.intent -o app.tnt
  5. Implement with @implements annotations
  6. Verify: ntnt intent check app.tnt
  7. Iterate with Intent Studio: ntnt intent studio app.intent

Check out the Learn page for installation instructions and a complete walkthrough.