nudgeDSL is a token-dense, human-readable Domain Specific Language for encoding executable intent from LLMs to multi-language backends (Go, Python, Dart).
An agent outputs a nudgeDSL string. A universal parser converts it to a JSON AST. Language-native executors read the AST and run the corresponding functions.
nudgeDSL does not: execute code directly, make network calls, or carry state between turns.
Format follows consumer. AI-consumed outputs are telegraphic. Human-consumed outputs are scannable. nudgeDSL is both at the same time.
An Atom is a short-code (1–3 uppercase characters) registered by the developer and mapped to a backend function. Atoms are always uppercase. The atom registry is injected into the agent context at runtime.
REGISTRY is a built-in atom that declares which domain registry a document expects. It must appear as the first expression if present. If the loaded registry does not match, the parser returns a RegistryError before evaluating any other atom.
An Operator encodes the relationship between atoms. Operators are fixed — part of the grammar, not redefinable by developers.
| Type | Syntax | Example | Notes |
|---|---|---|---|
| String | "value" | MARK("task-1") | No escaped quotes in v0.1 — use single-word identifiers or enums |
| Integer | bare number | MOV(3, 7) | May be negative |
| Float | decimal number | HEAL("self", 0.5) | May be negative |
| Boolean | true / false | LOCK(true) | Lowercase only |
| Null | null | RESET(null) | Lowercase only |
v0.1 constraint: Strings cannot contain escaped double quotes. Design atom arguments to avoid this — use single-word identifiers or enum values where possible.
| Operator | Name | Semantics | Failure behavior |
|---|---|---|---|
| >> | Chain | Execute left, then right. Right does not run if left fails. | Fail-fast (default) |
| | | Fallback | Try left. If left fails, try right. | Best-effort per branch |
| // | Parallel | Execute left and right concurrently. | Configurable (see §5) |
| **N | Amplify | Repeat the preceding atom N times sequentially. | Fail-fast |
** — Amplify — binds tightest, applies to immediately preceding atom only// — Parallel>> — Chain| — Fallback — binds loosestUse parentheses to override precedence. A() // B() ** 3 is parsed as A() // (B() ** 3) — amplify binds before parallel.
The // operator supports three configurable failure modes. Set at executor registration. Default is fail-fast.
If any branch fails, all remaining branches are aborted immediately. No partial state is applied.
All branches execute regardless of individual failures. Errors are collected and returned after all branches complete. Partial state is applied.
Each atom may declare a rollback atom in the registry. If a branch fails, the executor calls the rollback atom of each already-completed branch. The executor automatically passes the exact same arguments from the forward atom to its rollback atom.
Rollback atoms must accept the same argument signature as their forward atom. This is enforced at registry load time, not execution time. A misconfigured rollback that surfaces during a compensating transaction is worse than a startup error.
The parser always outputs this structure. Executors consume the AST — never the raw string.
{
"version": "0.1.0",
"root": {
"type": "chain",
"nodes": [
{
"type": "call",
"atom": "MARK",
"fn": "UpdateStatus",
"args": [
{ "type": "string", "value": "task-1" },
{ "type": "string", "value": "done" }
]
},
{
"type": "call",
"atom": "NOTIFY",
"fn": "BroadcastEvent",
"args": [
{ "type": "string", "value": "ops" }
]
}
]
}
}
| type | Fields |
|---|---|
call | atom, fn, args[] |
chain | nodes[] |
parallel | nodes[], failure_mode |
fallback | nodes[] |
amplify | node, count |
Developers register atoms before injecting context. The registry is the single source of truth for prompt generation and GBNF grammar compilation.
{
"domain": "my-project",
"version": "0.1.0",
"extends": [],
"atoms": [
{
"atom": "MARK",
"fn": "UpdateStatus",
"description": "Transition an item to a new status.",
"args": [
{ "name": "id", "type": "string" },
{ "name": "status", "type": "string",
"enum": ["pending", "done", "skipped", "error"] }
],
"rollback": null
},
{
"atom": "HEAL",
"fn": "HealUnit",
"description": "Heal the target unit by a percentage of max HP.",
"args": [
{ "name": "target", "type": "string",
"enum": ["self", "ally1", "ally2"] },
{ "name": "amount", "type": "float",
"min": 0.0, "max": 1.0 }
],
"rollback": "UNHEAL"
}
]
}
| Field | Applies to | Meaning |
|---|---|---|
min / max | integer, float | Inclusive range |
enum | string | Allowed values only |
required | all | Defaults to true |
If an atom declares a rollback, these checks run at registry load time:
type at the same positionFail at load time, not at execution time. A misconfigured rollback that surfaces during a compensating transaction is a silent correctness hole.
Parsing and validation are two separate steps. A string can be syntactically valid and semantically invalid.
raw string
│
▼
[ Parser ] ──── syntax error ──▶ ParseError
│
▼
JSON AST
│
▼
[ Semantic Validator ] ──── constraint violation ──▶ ValidationError
│
▼
[ Executor ]
nudgeDSL ships a DefaultValidator that enforces all registry-declared constraints. Developers may replace or extend it via the Validator interface.
All errors are structured. The parser and validator never panic.
| Code | Meaning |
|---|---|
| EMPTY_INPUT | Input string is empty or whitespace only |
| TRUNCATED_INPUT | Input ends mid-token — likely stream truncation |
| UNEXPECTED_TOKEN | Token does not fit the grammar at this position |
| UNTERMINATED_STRING | String opened with " but never closed |
| MISSING_CLOSE_PAREN | Atom call opened but ) never found |
| TRAILING_OPERATOR | Expression ends with an operator |
| UNKNOWN_ATOM | Atom not found in registry or not uppercase |
| Code | Meaning |
|---|---|
| ARG_OUT_OF_RANGE | Integer or float outside declared min/max |
| ARG_NOT_IN_ENUM | String not in declared enum values |
| ARG_TYPE_MISMATCH | Argument type doesn't match declaration |
| ARG_COUNT_MISMATCH | Wrong number of arguments for this atom |
| UNKNOWN_ATOM | Atom in AST absent from registry at validation time |
| Code | Meaning |
|---|---|
| ROLLBACK_NOT_FOUND | Rollback atom declared but not present in registry |
| ROLLBACK_SIGNATURE_MISMATCH | Arg count or type mismatch between forward and rollback |
| REGISTRY_MISMATCH | Document REGISTRY() declaration does not match loaded registry |
nudgeDSL generates the system prompt injection from the atom registry. Agents receive the generated prompt — not this spec.
You are operating with nudgeDSL v0.1.0. Output ONLY valid nudgeDSL strings. No explanation, no preamble, no markdown. ## Registered Atoms SHARD(id: string, phase: string) — Open a shard context. ACCEPT(criterion: string) — Add acceptance criterion. MARK(id: string, status: string) — Transition item status. NOTE(text: string) — Record decision note. ## Operators >> chain (sequential) | fallback (try left, then right) // parallel (concurrent) **N amplify (repeat N times) ## Constraints MARK.status: one of [pending, done, skipped, error, blocked] Output nudgeDSL only.
The nudgeDSL simulator takes existing agent output and produces the nudgeDSL equivalent, token comparison, inferred registry, and GBNF grammar.
nudge analyze ./output.json nudge analyze ./output.py --format python nudge analyze --stdin # with custom registries (last-defined wins on conflicts) nudge analyze ./output.json --registry core.json --registry nudge.json nudge analyze ./output.json --registry ./atoms.json
Note: savings_percent is always net (output + amortized system prompt). Gross savings are not reported — they are misleading.
This document is versioned. Breaking changes increment the minor version. The version field in every AST output references this spec version.
Agents and executors must agree on spec version. A mismatch is a runtime error, not a silent failure. Documents using REGISTRY() must declare the domain version — version mismatch returns REGISTRY_MISMATCH before any parsing occurs.
These are explicitly out of scope. Do not implement them in v0.1 parsers.
$result >> M($x, $y))IF(cond) >> A() | B())SHOOT() ** $n)@agent1 >> M(3,7))"extends" field is reserved, must be empty array in v0.1)nudgeDSL separates the grammar (this spec) from the vocabulary (atom registries). The grammar never changes within a major version. The vocabulary is fully customizable per project.
| Layer | File | Purpose |
|---|---|---|
| Core | nudgedsl-core.json | Generic atoms usable in any domain (MARK, CREATE, NOTIFY...) |
| Domain | nudgedsl-{domain}.json | Atoms for a specific methodology (SHARD, PHASE, ACCEPT...) |
| Project | atoms.json | Project-specific atoms, lives next to shards |
--registry flag wins silentlyRegistryError at load timenudgeDSL-spec-v0.1.0 — pre-build artifact. Implementation does not yet exist at time of writing.