The .warp Syntax (v0.1)
A reference for the textual form merchants and the AI builder write workflows in. This is the canonical serialization the compiler consumes.
File extension
.warp — one file declares exactly one project.
Project structure
A .warp file is a project block: a header (version, tenant) followed
by a sequence of typed node declarations that form the workflow graph.
project "cart_recovery" {
version = "1.0.0"
tenant = "tenant_aimer"
CartAbandoned trigger {
min_value: Currency(200, MAD)
after: Duration(30, minutes)
}
ACPGetCustomerProfile profile {
customer_id: trigger.customer_id
}
WhatsAppSend first_touch {
to: profile.phone
template: "cart_reminder"
lang: profile.language
}
DelayFor wait {
duration: Duration(24, hours)
}
ACPEvaluateStrategy offer {
customer_id: trigger.customer_id
}
WhatsAppSend followup {
to: profile.phone
template: "cart_offer"
lang: profile.language
params: { discount_code: offer.discount_code }
}
}
The structure mirrors what merchants see on the canvas:
- The
projectblock is the workflow. - Each declaration is one node.
<NodeType> <instance_name>identifies every node — the node type (PascalCase) tells the compiler which catalog entry to type-check against; the instance name (snake_case) is what later declarations dot-reference to read that node’s output.
Declaration order is for readability. The compiler reconstructs the
dependency graph from the instance.field references inside node bodies, not
from top-to-bottom position.
Tokens
| Token | Form | Example |
|---|---|---|
KEYWORD |
project, version, tenant |
project |
NODE_TYPE |
PascalCase identifier (a catalog node) | CartAbandoned, WhatsAppSend |
IDENTIFIER |
snake_case identifier |
trigger, first_touch |
STRING |
Double-quoted, supports \" and \\ |
"cart_reminder" |
CURRENCY |
Currency(<amount>, <CODE>) |
Currency(200, MAD) |
DURATION |
Duration(<amount>, <unit>) |
Duration(30, minutes) |
REFERENCE |
<identifier>.<field> |
profile.phone |
OBJECT |
{ key: value, … } |
{ discount_code: offer.code } |
| punctuation | { } : , = |
Node types vs. instance names are distinguished by case: PascalCase is reserved for node types, snake_case for instance names and field accesses.
Commerce types are first-class
Currency(200, MAD) and Duration(30, minutes) are single lexical tokens,
not function calls — commerce types are syntax, not a standard-library
prelude. The compiler lifts the amount + unit/code directly onto the typed
config value.
- Duration units (v0.1):
seconds,minutes,hours,days. - Currency codes (v0.1): three-letter uppercase;
MAD,EUR,USDare validated by the type checker.
Comments
Line comments start with // and run to end of line. Block comments are
reserved syntax (not in v0.1).
// fire only on carts worth recovering
CartAbandoned trigger {
min_value: Currency(200, MAD) // MAD is a real type, not a number
after: Duration(30, minutes)
}
What the compiler enforces
Beyond syntax, the compiler checks:
- Node types exist — every PascalCase node must be in the catalog.
- References resolve — every
instance.fieldmust name a node declared earlier (or the keywordtrigger, which refers to the first trigger node). - Required inputs are present — each node declares the config keys it requires.
- Commerce-model invariants — currency conservation, capacity verification, temporal order, identity permanence, and commitment-tree consistency. See the Commerce Model and the Getting Started guide.
Error philosophy
Every error names the token found, what was expected, and the 1-based line — because the compiler is the author’s ally, not an obstacle. For example:
Line 9: WhatsAppSend 'send' is missing required input 'to'.
There is no “error near <unknown>”. If the lexer emits a token, the parser
knows what it is and where it is.
Out of scope for v0.1
Loops, conditionals, and string interpolation are not in v0.1. When a workflow needs branching, compose it from multiple nodes. The grammar is intentionally narrow — small enough to ship and prove, wide enough to express real commerce recovery, post-purchase, and campaign workflows.