Develop
Building Shims
A NIL translation shim is a stateless HTTP adapter that translates NIL sentences into native API calls — letting a backend sit behind the agent plane without changing its own API. It enforces two-phase execution, idempotency, and compensation-aware reversibility.
What a shim is (and isn’t)
A shim exposes the six NIL endpoints, translates intent to native API calls, and pushes EVENT callbacks after writes. It is not a business-logic layer, not a cache, and not a NIL client — it is a thin, stateless boundary.
Three decoupled layers
- NIL edge. HTTP, envelope validation, auth.
- Translation core. Pure mapping functions, one per verb.
- System client. The native API calls to your backend.
Keep the translation core pure and unit-tested; keep the edge generic and reusable across backends.
Minimal state
The shim keeps only what correctness demands:
- An idempotency ledger:
idempotency_key → terminal outcome. - A proposal store:
proposal_id → { verb, resolved args, expiry, state }. - An EVENT sequence counter, monotonic per workspace.
The six endpoints
| Sentence | Method + path | Your response |
|---|---|---|
| PROPOSE | POST /nil/v0.1/propose | Envelope PROPOSAL (proposal or refusal) |
| COMMIT | POST /nil/v0.1/commit | Envelope STATUS |
| QUERY | POST /nil/v0.1/query | Bare { "data": { … } } — not an envelope |
| STATUS | GET /nil/v0.1/status/{id} | Envelope STATUS |
| EVENT | outbound webhook | You are the sender |
| ROLLBACK | POST /nil/v0.1/rollback | Envelope PROPOSAL (compensation preview) |
PROPOSE — validate, never write
PROPOSE maps the verb to native validation, resolves references, and returns either a proposal or a refusal — with zero side effects. The server computes the authoritative values (amounts, tax, inventory); the caller’s hints are ignored. Persist the proposal for the later COMMIT.
// success
{ "outcome": "proposal", "id": "<url-safe 8-128>", "verb": "...",
"tier": "LOW|MEDIUM|HIGH|CRITICAL", "preview": { "ar": "…", "en": "…" },
"resolved": { … }, "modifiable": ["…"], "expires_at": "<RFC3339>" }
// cannot satisfy
{ "outcome": "refusal", "code": "AMBIGUOUS|UNRESOLVED|INVALID_ARGS|…",
"message": "…", "field": "party_id",
"candidates": [ { "id": "…", "name": "…" } ] }AMBIGUOUS refusal must carry candidates (≤8).COMMIT — idempotent execution
COMMIT receives { proposal_id, idempotency_key }. Check the ledger first: if the key is present, return the stored terminal outcome — no duplicate write. Load the stored proposal; if expired or suspended, refuse with PROPOSAL(EXPIRED|SUSPENDED). For LOW/MEDIUM, answer STATUS(executing|executed); for HIGH+, park with STATUS(pending_approval). The actual write happens at execution and is reported via EVENT, not in the HTTP response.
ROLLBACK & reversibility
ROLLBACK is a request for a governed reversal, answered by a PROPOSAL previewing the compensation. Declare each verb’s reversibility tier. Resolve the compensation_token; if unknown or expired, refuse COMPENSATION_EXPIRED. The compensation lands only on the following COMMIT.
ROLLBACK honestly — zero-touch for existing adapters, and never a phantom reversal.EVENT — reporting the outcome
After execution, push an EVENT to the agent plane’s webhook. It must carry proposal and result.entity, be signed with HMAC-SHA256 over the raw body, and include a monotonic per-workspace sequence header.
{ "event": "executed", "severity": "info", "proposal": "<id>",
"result": { "claim": "success", "changed": true, "verified": true,
"entity": { "type": "invoice", "id": "…", "url": "…" },
"ssot": { "system": "<your backend>", "read_after_write": true } } }Definition of done
- All six endpoints, behind grant/bearer auth.
- PROPOSE produces zero side effects; invalid or ambiguous args refuse (never an HTTP error).
- Server-authoritative fields; the caller’s hints never become facts.
- COMMIT is idempotent and emits a signed, sequenced EVENT.
- QUERY returns bare data; destructive verbs require an explicit grant.
- The conformance matrix passes; the translation core is pure and unit-tested.