<nil>NILScript

Core Concepts

Architecture

The tri-layer machine separates concerns chat collapses into one: Generation emits plans, Validation proves them safe, Runtime executes them durably. Each layer has one job; no layer is trusted to do another's.

The three layers

NILScript separates capability by design. Generation produces text but executes nothing. Validation forbids unsafe graphs without acting. Runtime owns side effects, and only through NIL. The core inversion: the part that is creative is not allowed to act, and the part that acts is not creative.

LayerRoleAnalogyTrust boundary
1. GenerationNL intent → DSL (JSON DAG)Compiler front-halfProduces text; executes nothing
2. ValidationStatic analysis before any side effectStatic gateRejects unsafe graphs
3. Durable RuntimeInterprets the DAG, drives NIL per edgeVirtual machineOwns side effects; persists state

Layer 1 — Generation

The LLM is the compiler front-end, writing DSL programs. Models emit symbolic references ($.step_1.output.id) to values they cannot know yet — intent, not implementation. Three properties keep it safe despite an unpredictable model: no side-effect channel, self-healing input, and least power downstream.

Generation is powerless by design
A correct generator emits a plan it does not have the authority — or the data — to carry out.

Layer 2 — Validation

The validator runs before any side effect and rejects unsafe graphs through five checks: schema validation (additionalProperties: false), backward-only reference tracking, an acyclicity proof, whitelist enforcement against grant scopes, and type checking against verb profiles. On failure it returns structured diagnostics the generator reads to self-heal.

validation-result.json
{
  "valid": false,
  "diagnostics": [
    {
      "code": "REF_UNRESOLVED",
      "node": "step_3",
      "path": "$.step_2.output.invoice_id",
      "message": "step_2 (type: condition) produces no output; conditions route only.",
      "hint": "Reference the action node that creates the invoice, or remove the dependency."
    }
  ]
}

Layer 3 — Durable Runtime

The runtime is the VM. It walks the validated DAG node by node, converting each action into a NIL PROPOSE then — after any approval — a COMMIT. Queries become QUERY; conditions route only; parallel and foreach fan out; await_approval blocks for a DECIDE. On terminal failure with on_error: compensate, it walks back via ROLLBACK.

Stateless chat → durable orchestration

A chat turn vanishes when the socket closes; a NILScript program persists. The runtime saves state after every node, so a crash between node 3 and node 4 resumes at node 4 — not at the sentence. Control flow is deterministic and replayable; non-determinism is quarantined into the NIL activities, the only place the outside world is allowed to be unpredictable. Every COMMIT carries an idempotency key, so a retry across a network edge replays the original outcome instead of acting twice.

A worked example

The sentence: “When a product drops below 5 units, reorder 50 from the default supplier, but only let me approve it if the order is over 1,000 SAR.” Generation emits a DSL program; validation admits it; the runtime executes. For the action node, the runtime sends a PROPOSE — which has no side effects:

PROPOSE
{
  "nil": "0.1",
  "id": "msg_01H...",
  "performative": "PROPOSE",
  "grant": "grant_acme_agent",
  "workspace": "ws_acme",
  "timestamp": "2026-06-16T09:00:00Z",
  "trace": "00-4bf92f...-00f067aa0ba902b7-01",
  "body": {
    "verb": "commerce.create_purchase_order",
    "args": { "supplier_hint": "default", "sku": "SKU-1042", "quantity": 50 }
  }
}

The system resolves the facts itself. The order totals 1,250 SAR, crosses the threshold, and the profile floors the tier at HIGH — so it parks for approval:

PROPOSAL
{
  "nil": "0.1",
  "performative": "PROPOSAL",
  "body": {
    "outcome": "preview",
    "tier": "HIGH",
    "proposal_id": "prop_01H...",
    "preview": {
      "ar": "إنشاء أمر شراء: 50 وحدة من المورد «شركة الإمداد» بقيمة 1,250.00 ر.س",
      "en": "Create purchase order: 50 units from supplier 'Imdad Co.' for SAR 1,250.00"
    },
    "resolved": { "supplier": "sup_88", "total": "1250.00", "currency": "SAR" }
  }
}
The preview is computed, not echoed
The total and supplier come from the system, not from the agent’s supplier_hint. The owner reviews out of band and issues a DECIDE; only then does the runtime COMMIT with an idempotency key, and the system emits a signed EVENT.

What each layer guarantees

PropertyGuaranteed byHow
Can’t act on a hallucinationGeneration + NILNo side-effect channel; system resolves facts
Unsafe graphs never runValidationStatic rejection before any commit
Same plan, same flowRuntimeDeterministic walk; non-determinism quarantined
Crash doesn’t double-actRuntime + NILPersisted state + idempotency replay
Every write is reviewableRuntime + NILPreview from system facts; tiers; approval

NILScript is an open standard, stewarded by the Wosool project. The spec is extracted from running code.

Draft standard v0.3.0 · 0.x stage · NIL wire 0.1 · DSL 0.1