v0.9.2 · experimental

The typed IR for AI agents.

Define tools once. Export to OpenAI, Anthropic, Gemini — with effect-safe filtering built in.

$ pip install nail-lang

Three problems. One solution.

Building tool-using AI agents today means fighting the ecosystem. NAIL is the missing layer.

🔀
Provider chaos
OpenAI, Anthropic, Gemini — three schemas, three formats, three different ways to define the same tool. Maintaining all three is dead weight.
→ NAIL converts from one source.
🔒
No permission model
MCP servers can do anything. There's no built-in way to say "this agent can read files, but not hit the network."
→ Effect labels (NET/FS/IO) + qualifiers (scope/trust) block at check time, not runtime.
Ambiguous AI output
Tool schemas generated by different prompts look different. Canonical form doesn't exist — until you enforce it.
→ The same spec generates token-identical output. Every time.

One definition. Three providers.

Write your tool spec in NAIL once. nail fc convert outputs the correct schema for any provider.

# tools.nail

fn search_web(
  query: String,
  max_results: Int = 10
) @NET -> List[Result] {
  "Search the web and return ranked results."
}

fn read_file(
  path: String,
  encoding: String = "utf-8"
) @FS -> String {
  "Read a file from disk and return its contents."
}
# nail fc convert tools.nail --provider openai

[
  {
    "type": "function",
    "function": {
      "name": "search_web",
      "description": "Search the web and return ranked results.",
      "parameters": {
        "type": "object",
        "properties": {
          "query":       { "type": "string" },
          "max_results": { "type": "integer", "default": 10 }
        },
        "required": ["query"]
      }
    }
  }
]
# nail fc convert tools.nail --provider anthropic

[
  {
    "name": "search_web",
    "description": "Search the web and return ranked results.",
    "input_schema": {
      "type": "object",
      "properties": {
        "query":       { "type": "string" },
        "max_results": { "type": "integer", "default": 10 }
      },
      "required": ["query"]
    }
  }
]
# nail fc convert tools.nail --provider gemini

{
  "function_declarations": [
    {
      "name": "search_web",
      "description": "Search the web and return ranked results.",
      "parameters": {
        "type": "OBJECT",
        "properties": {
          "query":       { "type": "STRING" },
          "max_results": { "type": "INTEGER" }
        },
        "required": ["query"]
      }
    }
  ]
}

Sandbox your agents.

Declare what your tools can do. Block everything else — at check time, before a single token is generated.

python
from nail_lang import filter_by_effects

# Allow only file system and I/O — block network and process calls
safe_tools = filter_by_effects(all_tools, allowed=["FS", "IO"])
# → Blocks NET, PROC. Unannotated tools excluded by default.

response = client.chat.completions.create(
    tools=safe_tools,
    messages=messages,
    ...
)
@NET
External network access. HTTP, DNS, WebSocket.
@FS
File system read or write. Local paths only.
@IO
General I/O. Stdin, stdout, device access.
@PROC
Subprocess or shell execution. Highest risk.

Verify before you run.

NAIL checks your tool specs at four levels before they ever reach a model.

L0

Schema validation

JSON schema conformance check. Malformed specs are rejected immediately.

L1

Type checker

Parameter types resolved: int / string / bool. No implicit coercions.

L2

Effect checker

IO in a pure function is a compile error. Effects must be declared explicitly.

L3

Termination proof

Any loop in a tool spec must provably halt. Infinite loops are rejected at compile time.

Up in 60 seconds.

Install, check your existing tools, and convert to any provider — all from the CLI.

terminal
pip install nail-lang

# Check tools against a provider
nail fc check tools.nail --provider openai

# Convert to any provider
nail fc convert tools.nail --provider anthropic > tools.json
nail fc convert tools.nail --provider gemini   > tools.json

# Import existing schemas
nail fc import openai_tools.json --from openai
Try in Playground → Read the docs ↗