# NAIL

[![CI](https://github.com/watari-ai/nail/actions/workflows/ci.yml/badge.svg)](https://github.com/watari-ai/nail/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/nail-lang)](https://pypi.org/project/nail-lang/)

![NAIL effect system — data exfiltration blocked before execution](demos/nail_killer_demo.gif)

NAIL is a CLI tool that lets you:

- Define AI tool schemas once in a typed IR
- Statically validate types and effects before runtime
- Generate OpenAI, Anthropic, and Gemini tool schemas from one source

## Install

```bash
# Recommended — works everywhere, no env conflicts
pipx install nail-lang

# Or with pip (inside a virtualenv, or non-Homebrew Python)
pip install nail-lang
```

## Quick Start

```bash
# Define your tools once (NAIL is JSON + an effects field)
cat tools.nail

# Validate against a specific provider
nail fc check tools.nail --provider openai

# Export to any provider
nail fc convert tools.nail --provider anthropic > anthropic_tools.json
nail fc convert tools.nail --provider gemini   > gemini_tools.json

# Import existing tool schemas into NAIL format
nail fc import openai_tools.json --from openai
```

Try it now → [naillang.com](https://naillang.com)

## Inspect & Validate with nail-lens

`nail-lens` is a companion CLI for human-readable inspection and validation of NAIL spec files — useful for reviewing LLM-generated specs before running them.

```bash
# Inspect a spec file (human-readable summary)
nail-lens inspect tools.nail

# Validate spec version and effects (default: L2)
nail-lens validate tools.nail

# Show all effects declared
nail-lens effects tools.nail

# Diff two spec files
nail-lens diff v1.nail v2.nail
```

Installed automatically with `nail-lang` (`pipx install nail-lang`).

## Why NAIL?

Function calling schemas differ across providers:

```
OpenAI:    { "type": "function", "function": { "parameters": {...} } }
Anthropic: { "name": "...", "input_schema": {...} }
Gemini:    { "name": "...", "parameters": {...} }
```

NAIL gives you one source of truth. Define once, export anywhere.

The **effect system** (`"effects": ["NET", "FS"]`) lets you declare what a tool
can do — network access, file I/O, etc. — and catch violations before the agent runs.

```bash
$ nail fc check tools.nail --provider openai
✗ tools.nail  [3 tool(s)]  provider=openai
  ERROR: Tool 'exfil_data': declared as PURE but has side-effect labels: ['NET']
```

### Spec versioning with `meta.spec_version`

Every NAIL spec file can declare its specification version via the `meta.spec_version` field:

```json
{"nail": "0.1.0", "kind": "fn", "id": "add", "meta": {"spec_version": "0.9.0"}, ...}
```

The checker validates this field at L0: unknown formats produce `CheckError(code="UNSUPPORTED_SPEC_VERSION")`. Specs without the field run in `legacy_mode` (warning only — backward-compatible). This allows tooling and alternative implementations to assert which specification guarantees apply.

## How is NAIL different from...?

> These questions come up constantly. Here are direct answers.

### vs. TypeSpec (Microsoft)

| | TypeSpec | NAIL |
|---|---|---|
| Primary goal | Define REST/GraphQL APIs | Define AI tool schemas + enforce effect isolation |
| Syntax | TypeScript-like text | JSON (AI-writable, no parser needed) |
| Effect system | ❌ None | ✅ IO/NET/FS tracked at type level |
| Cross-provider FC | ❌ Not applicable | ✅ OpenAI / Anthropic / Gemini from one source |
| AI-native | ❌ Human-first | ✅ Designed for LLMs to generate/consume |

TypeSpec is excellent for API-first development by human engineers. NAIL is designed for AI agents to *write* and *verify* their own tool definitions — a different problem.

---

### vs. OpenAPI / JSON Schema

| | OpenAPI / JSON Schema | NAIL |
|---|---|---|
| Describes | REST endpoints / data structures | AI function calls (tool use) |
| Effect system | ❌ None | ✅ Effect isolation enforced |
| Verification layers | ❌ Schema validation only | ✅ L0 (schema) → L1 (types) → L2 (effects) → L3 (termination) |
| Multi-provider | ❌ No concept of "providers" | ✅ Native OpenAI / Anthropic / Gemini converters |
| Termination proofs | ❌ Not applicable | ✅ L3 emits a halt certificate |

OpenAPI describes *what a service exposes*. NAIL describes *what an agent can call* — and proves it's safe to call.

---

### vs. Pydantic

| | Pydantic | NAIL |
|---|---|---|
| Language | Python-specific | Language-agnostic (JSON) |
| Effect system | ❌ None | ✅ First-class, enforced at check time |
| Multi-provider FC | ❌ Manual adapters needed | ✅ Built-in converters |
| AI-writable | ❌ Python syntax required | ✅ Pure JSON, no parser |
| Termination proofs | ❌ Not applicable | ✅ L3 checker |

Pydantic validates Python objects at runtime. NAIL validates *AI tool schemas* — across providers, before execution, including effect isolation.

---

### vs. Rego / OPA

| | Rego / OPA | NAIL |
|---|---|---|
| Domain | Policy enforcement (access control) | AI tool definitions + effect typing |
| Effect system | ❌ Policy rules, not typed effects | ✅ IO/NET/FS in function signatures |
| Multi-provider FC | ❌ Not applicable | ✅ Native converters |
| AI-native | ❌ Human-authored policies | ✅ Designed for LLMs |

Rego excels at answering "is this action allowed?" NAIL answers "does this tool *declaration* have the right effect signature?" — a static, pre-execution guarantee.

---

**TL;DR:** NAIL occupies a gap none of these fill: *AI-native, effect-typed, multi-provider function calling with static verification.*

## How It Works

| Layer | What it checks |
|-------|----------------|
| L0    | JSON structure (schema) |
| L1    | Type correctness |
| L2    | Effect declarations |
| L3    | Termination proofs |

[▶ Full documentation](docs/) · [▶ Playground](https://naillang.com)

---

<!-- Full README continues below -->

<details>
<summary>📖 Full documentation — click to expand</summary>

## What is NAIL?

NAIL is a programming language designed for AI agents to write, verify, and exchange — not for humans to read.

**Three things NAIL solves that no other language does:**

1. **Effect-safe tool routing** — Declare `"effects": ["NET"]` on a tool; NAIL enforces it. AI agents can't call a network tool from a pure sandbox. Enforced at check time, not runtime.
2. **Verifiable AI output** — L0/L1/L2/L3 checkers catch type errors, effect violations, and infinite loops *before* execution. AI-generated code that passes NAIL check is correct for all checked properties (types, effects, and termination under declared annotations).
3. **Cross-provider Function Calling** — `nail_lang.fc_standard` converts NAIL function definitions to OpenAI / Anthropic / Gemini schemas. Write once, deploy to any provider. (v0.8.0)

Modern AI systems generate code and call tools at scale. NAIL gives that scale a formal foundation.

## Core Guarantees

| | Guarantee | Example |
|---|---|---|
| **Zero Ambiguity** | The same spec generates identical code every time | RFC 8785-inspired canonical subset: `json.dumps(sort_keys=True, ensure_ascii=False, separators=(',', ':'))` — one representation, always (non-ASCII preserved as-is) |
| **Effect System** | Side effects tracked at the type level | `fn main [] → fn helper [IO]` is a compile-time error, not a lint warning |
| **Verification Layers** | AI-written code passes 3 independent checks before running | L0 (schema) → L1 (types) → L2 (effects) — all enforced, no silent passes |

## Core Design Principles

1. **AI-first, human-second** — Written and maintained by AI. Human developers interact at the specification layer, not the code layer.
2. **Zero ambiguity** — One way to express every construct. No implicit behavior. No undefined behavior. Enforced by an RFC 8785-inspired canonical subset.
3. **Effects as types** — All side effects (IO, network, filesystem) are declared in function signatures and enforced by the type system.
4. **Verification layers (L0–L2)** — Every program passes schema, type, and effect checks before execution. No silent passes.
5. **Formal verification (v0.6+)** — `nail check --level 3` emits a termination certificate. Provably guaranteed to halt.
6. **Self-evolving** — The language specification itself is developed and improved by AI, with humans providing intent and constraints.

## FAQ: Is NAIL just a JSON AST?

Short answer: no — but it's a fair question.

Most languages use an AST as an *internal* representation. NAIL uses JSON as its **only** representation — there is no text syntax that compiles to it.

What makes NAIL different from "JSON-serialized AST":

**1. The verifier layers are the language.**
The JSON schema (L0), type checker (L1), and effect checker (L2) are not tools built *on top of* NAIL — they *are* NAIL. A program passing all three layers is correct for all checked properties (types, effects, and termination under declared annotations).
Layering is intentional: L0 is minimal by design, while L1/L2 enforce semantic correctness. A termination certificate (L3) is issued when all loops are bounded and all recursive calls carry a strictly decreasing `measure` annotation.

| Layer | Responsibility |
|---|---|
| **L0 (Schema)** | Minimum structural validity — accepts correctly shaped JSON programs |
| **L1 (Type Checker)** | Type correctness — catches int/string mismatches and undefined variables |
| **L2 (Effect Checker)** | Effect isolation — IO in pure functions is a compile-time error |

**2. Effects as first-class types.**
Every function declares its side effects (`io`, `net`, `fs`) in its signature. Calling an IO function from a pure context is a compile-time error — not a lint warning, not a runtime panic.

```json
{"nail":"0.2","kind":"module","defs":[
  {"id":"log_it","effects":["IO"],"params":[{"id":"x","type":{"type":"int","bits":64,"overflow":"panic"}}],"returns":{"type":"unit"},"body":[]},
  {"id":"pure_fn","effects":[],"params":[],"returns":{"type":"unit"},"body":[
    {"op":"call","fn":"log_it","args":[{"lit":1}]}
  ]}
]}
```

```
$ nail check above.nail
CheckError: call to 'log_it' requires effects [IO], but 'pure_fn' only has []
```

No runtime needed. The effect contract is violated at check time.

**3. Canonical form.**
There is exactly one valid JSON representation for any given program. No formatting choices, no style variants. An LLM generating the same logic twice will produce token-for-token identical output.

This is enforced by an RFC 8785-inspired canonical subset (sorted keys + compact separators + `ensure_ascii=False`; does not claim full RFC 8785 compliance): `nail canonicalize` normalizes any NAIL program to its canonical form using `json.dumps(sort_keys=True, ensure_ascii=False, separators=(',', ':'))`, preserving non-ASCII characters as-is rather than escaping them. `nail check --strict` rejects non-canonical input. Example files are stored in canonical form.

**4. Designed for LLM *generation*, not LLM *reading*.**
NAIL is not optimized for an LLM to read existing code. It is optimized for an LLM to write new code: zero ambiguity, zero implicit behavior, zero hallucination surface area.

The analogy: SQL is "just text for querying tables," but the relational model and declarative semantics are what make it SQL — not the text format.

## FAQ: Why JSON and not S-expressions (Lisp)?

Modern LLMs have JSON structured output modes built in (OpenAI, Anthropic, Google all provide `response_format: json`). JSON is the de facto interchange format of AI systems in 2026. Using S-expressions would make NAIL "typed Scheme with effects" — a 60-year-old idea without the novelty.

JSON-as-AST is the differentiator. The canonical form guarantee (`nail canonicalize`) is only possible because JSON has well-defined serialization semantics (NAIL uses an RFC 8785-inspired subset: sorted keys + compact separators). S-expressions have no such standard.

## Python SDK

`nail-lang` ships a full Python SDK (`pip install nail-lang`). Type stubs are included for IDE completion and mypy/pyright support.

### Verify & run NAIL programs

```python
from nail_lang import Checker, Runtime, CheckError

spec = {
    "nail": "0.8.0", "kind": "fn", "id": "add",
    "effects": [], "params": [
        {"id": "a", "type": {"type": "int", "bits": 64, "overflow": "panic"}},
        {"id": "b", "type": {"type": "int", "bits": 64, "overflow": "panic"}},
    ],
    "returns": {"type": "int", "bits": 64, "overflow": "panic"},
    "body": [{"op": "return", "val": {"op": "+", "l": {"ref": "a"}, "r": {"ref": "b"}}}],
}

try:
    Checker(spec, level=3).check()   # L0 schema + L1 types + L2 effects + L3 termination
except CheckError as e:
    print(e.to_json())  # machine-parseable error with code, location, message

result = Runtime(spec).run({"a": 3, "b": 4})
print(result)  # → 7
```

### Effect-safe tool routing

NAIL's effect system can be used directly in Python to sandbox AI agent tool lists:

```python
from nail_lang import filter_by_effects

tools = [
    {"type": "function", "function": {"name": "read_file",  "effects": ["FS"]}},
    {"type": "function", "function": {"name": "http_get",   "effects": ["NET"]}},
    {"type": "function", "function": {"name": "exec_script","effects": ["PROC"]}},
    {"type": "function", "function": {"name": "log",        "effects": ["IO"]}},
]

# Restrict agent to read-only: no network, no process execution
safe_tools = filter_by_effects(tools, allowed=["FS", "IO"])
# → [read_file, log]

# Pass to LiteLLM, OpenAI, or any provider
response = litellm.completion(model="gpt-4o", tools=safe_tools, ...)
```

Unannotated tools are excluded by default (production-safe). See [`integrations/litellm.md`](integrations/litellm.md) for the full integration guide.

### SDK public API

| Symbol | Description |
|---|---|
| `Checker(spec, level=2)` | Verify a NAIL program (L0–L3) |
| `Runtime(spec)` | Execute a verified NAIL program |
| `CheckError` | Structured check-time error (`.to_json()` for machine parsing) |
| `filter_by_effects(tools, allowed)` | Restrict tool list to an effect scope |
| `get_tool_effects(tool)` | Introspect declared effects on a tool |
| `annotate_tool_effects(tool, effects)` | Add effects annotation to a tool |
| `validate_effects(effects)` | Validate effect label list |
| `from_mcp(tool)` / `to_mcp(tool)` | MCP ↔ FC-Standard conversion |
| `to_openai_tool` / `to_anthropic_tool` / `to_gemini_tool` | NAIL → provider conversion |
| `convert_tools(tools, to="anthropic")` | Batch provider conversion |
| `parse_type(spec)` | Parse a type descriptor dict into a `NailType` |
| `VALID_EFFECTS` | `frozenset` of recognised effect kinds |

## FC Standard — Cross-Provider Function Calling

NAIL v0.8.0 introduces `nail_lang.fc_standard`: a unified converter between NAIL function definitions and OpenAI / Anthropic / Gemini Function Calling schemas.

```python
from nail_lang.fc_standard import convert_tools, to_openai_tool, to_anthropic_tool, to_gemini_tool

nail_fn = {
    "nail": "0.8",
    "kind": "fn",
    "id": "search_web",
    "effects": ["NET"],
    "params": [{"id": "query", "type": {"type": "string"}}],
    "returns": {"type": "string"},
    "description": "Search the web and return results"
}

openai_tool    = to_openai_tool(nail_fn)    # OpenAI tools format
anthropic_tool = to_anthropic_tool(nail_fn) # Anthropic tools format
gemini_tool    = to_gemini_tool(nail_fn)    # Gemini functionDeclarations format

# Round-trip guaranteed: NAIL → provider → NAIL preserves structure
```

Write once, deploy to any provider. Effect annotations are preserved across conversions.

See [`nail_lang/fc_standard.py`](nail_lang/fc_standard.py) and the [FC Standard section in SPEC.md](SPEC.md).

## Status

🧪 **Experimental — v0.9.0** on PyPI — `pipx install nail-lang`

| Feature | Status |
|---|---|
| Types: int/float/bool/string/option/list/map/unit | ✅ Implemented |
| Effect system (IO/FS/NET/TIME/RAND/MUT) | ✅ Implemented |
| RFC 8785-inspired canonical subset + `nail canonicalize` + `--strict` | ✅ Implemented |
| `kind: fn` + `kind: module` + function calls | ✅ Implemented |
| Mutable variables (`let mut` + `assign`) | ✅ Implemented |
| Bounded loops + if/else | ✅ Implemented |
| Recursion/cycle detection | ✅ Implemented |
| Return-path exhaustiveness check | ✅ Implemented |
| L0 JSON Schema + L1 Type + L2 Effect checks | ✅ Implemented |
| Overflow modes: `wrap` / `sat` / `panic` | ✅ Implemented (v0.3) |
| Result type (`ok`/`err`/`match_result`) | ✅ Implemented (v0.3) |
| Cross-module import + effect propagation | ✅ Implemented (v0.3) |
| **Type aliases** (module-level `types` dict, circular detection) | ✅ Implemented (v0.4) |
| **Fine-grained Effect capabilities** (path/op allow-lists) | ✅ Implemented (v0.4) |
| **Collection type operations** (`list_get/push/len`, `map_get`) | ✅ Implemented (v0.4) |
| `read_file` (FS) / `http_get` (NET) | ✅ Fully implemented (v0.4) |
| Enum / ADT (`enum_make` / `match_enum`) | ✅ Implemented (v0.5) |
| Core StdLib (`abs`/`clamp`/`min2`/`max2`/`str_len`) | ✅ Implemented (v0.5) |
| FC effect annotations (tool sandbox metadata) | ✅ Implemented (v0.5) |
| **L3 Termination Proofs** (`nail check --level 3`) | ✅ Implemented (v0.6) |
| **`nail check --format json`** (machine-parseable output) | ✅ Implemented (v0.7) |
| **Generics** (`type_params` + `{"type": "param", "name": "T"}`) | ✅ Implemented (v0.7) |
| **Python API** (`nail_lang.filter_by_effects`) | ✅ Implemented (v0.7) |
| **import `"from"` file resolution** | ✅ Implemented (v0.7) |
| Structured JSON errors (`to_json()` / error codes) | ✅ Implemented (v0.7) |
| **Generic type aliases** (module-level `type_params`) | ✅ Implemented (v0.7.2) |
| **FC Standard** (`nail_lang.fc_standard`) | ✅ Implemented (v0.8.0) |
| **Provider converters** (NAIL ↔ OpenAI / Anthropic / Gemini) | ✅ Implemented (v0.8.0) |
| **MCP Bridge** (`from_mcp` / `to_mcp` / `infer_effects`) | ✅ Implemented (v0.7) |
| **Type stubs** (`nail_lang/__init__.pyi`) | ✅ Implemented (v0.8.2) |
| **L3.1: Call-site measure verification** (recursive `measure - k` proof) | ✅ Implemented (v0.8.2) |
| `nail demo` exit code propagation | ✅ Fixed (v0.8.2, #82) |
| Traits / Interfaces / Higher-kinded types | 🔮 Future |
| L4: Memory safety (buffer overflow proofs) | 🔮 Future |

## Secondary Effects: Token Efficiency

A byproduct of NAIL's minimal, unambiguous design is reduced token usage. In a Phase 2 validation experiment (2026-02-22), an LLM implemented the same 5 tasks in both Python and NAIL.

| Metric | NAIL | Python |
|---|---|---|
| Spec validation (L0–L2) | **5/5 (100%)** | N/A |
| Test pass rate | 18/21 (86%) | 21/21 (100%) |
| Avg tokens per function | **173** | 571 |
| Type annotations | Always required (compile error) | Optional |

NAIL used ~70% fewer tokens per function — a secondary benefit of the zero-ambiguity design, not its primary goal. All NAIL failures traced to spec gaps (not AI errors).

→ Full results: [`experiments/phase2/ANALYSIS.md`](./experiments/phase2/ANALYSIS.md)

## Structure

```
nail/
├── SPEC.md          — Language specification
├── PHILOSOPHY.md    — Design rationale and background
├── ROADMAP.md       — Development phases
├── CLI.md           — CLI command reference
├── demos/           — Demo scripts (including e2e_agent_demo.py)
├── examples/        — Sample NAIL programs
│   └── demos/       — Self-contained demos: API routing, agent handoff, verify-fix loop
├── interpreter/     — Python interpreter (Checker + Runtime)
├── playground/      — Local FastAPI playground (server-based)
├── docs/            — GitHub Pages static playground (Pyodide/WASM)
└── AGENTS.md        — AI agent instructions for this repo
```

## Playground

### 🌐 Online

Try NAIL instantly in your browser — no installation required:

**[https://naillang.com](https://naillang.com)**

Powered by [Pyodide](https://pyodide.org) — the Python interpreter compiled to WebAssembly. The NAIL interpreter runs entirely client-side.

### 💻 Local (FastAPI)

```bash
cd playground
python server.py
# → open http://127.0.0.1:7429
```

Features: live JSON editor, 20+ built-in examples, argument passing, dark theme.
See [`playground/README.md`](./playground/README.md) for details.

## Examples & Demos

Three self-contained demos showing NAIL solving real AI engineering problems — no API keys required:

| Demo | What it shows | Key API |
|------|--------------|---------|
| [API Routing](./examples/demos/api_routing/) | Convert one spec → OpenAI / Anthropic / Gemini | `convert_tools()` |
| [Agent Handoff](./examples/demos/agent_handoff/) | Slice tool registry by effect for each agent role | `filter_by_effects()` |
| [Verify-Fix Loop](./examples/demos/verify_fix_loop/) | LLM generate → Checker error → auto-fix → retry | `Checker.check()` |

```bash
# Run all demo tests
python3 -m pytest examples/demos/ -v   # 23 tests, all pass
```

## Quick Start

**Browser — no install:**
→ [https://naillang.com](https://naillang.com)

**CLI:** `pipx install nail-lang`

**Requirements:** Python 3.10+

**Clone & run:**
```bash
git clone https://github.com/watari-ai/nail.git
cd nail
pip install -r requirements.txt
./nail run examples/hello.nail
```

---

→ **[CLI.md](./CLI.md)** — full command reference (`nail run`, `nail check`, `nail canonicalize`)

---

See [CONTRIBUTING.md](./CONTRIBUTING.md) to contribute.

*NAIL is built by AI, for AI. Humans define the intent. AI builds the machine.*

</details>
