The Ludwig Compiler

Click here to return to the Advanced Ludwig table of contents.

lwc is the compiler for the Ludwig language. This guide details lwc usage, arguments, options, and examples.


lwc <FILE> [options]


Ludwig file to compile


-h | --help
Show help text.
-s | --semantics <BACKEND>

Select output backend. Note: Only two semantic backends are recommended for users: -s null and -s simple. -s null displays no output if there are no errors, and is the default backend. -s simple displays JSON output of the compiled composition, or errors if they exist. Other backends are designed for Fugue developer use only.


  • null: No output displayed. Default.
  • tree: Output a flat, tree-structured, strongly normalized form. All variables are inlined and replaced with their definitions. *
  • graph: Deprecated. *
  • proto: Output protocol buffer code. Note: Requires -o | --output-file option. *
  • simple: Output a JSON rendering of the proto version. The output contains UUIDs.
  • snapshot: Output an archive file that can be used to run a composition again. Users who want to re-run a composition can simply execute fugue run again. Note: Requires -o | --output-file option. *
  • swagger: Reserved for future functionality.
Compile a Ludwig file strictly as a composition.
-d | --dump <DUMP1:DUMP2>

Dump debug info to stderr. *


  • modules: Module file paths
  • constraints: Inferred type constraints
  • core: Program core
  • lexer: Lexer output
  • operators: Operator table
  • profiling: Timing information
  • restructure: Graph after restructuring
  • types: Inferred types
  • logs: Logs
  • env: Environment variables used in the composition (via String.getEnv)
  • files: Files used in the composition (via String.readFileUtf8 or Bytes.readFile)
  • evalprofiling: Evaluation timing information
-i | --include-dirs DIR1:DIR2
Include additional search directories for modules.
Run the typechecker only. *
-w | --warnings
Show warnings (default).
Don’t show warnings.
Exit on warnings.
Enable colorized output (default).
Disable colorized output.
--timeout SECONDS
Set length of compilation timeout, in seconds. Default: 10 seconds.
-o | --output-file FILE
Set output file. Required for proto and snapshot backends. Optional for text-based backends. snapshot output files must have a .tar.gz extension.
--validation-modules MODULE1:MODULE2
Specify validation module names.
--set "binding: expr"
Override a Ludwig binding. The binding must have the @override annotation.
--environment-write FILE
Create a JSON file with environment variables and file mapping of the current environment. *
--environment-read FILE
Specify JSON file containing environment variables and file mapping. For use with snapshots. *
--error-format {text | json}
Set format for displaying errors and warnings. Use text for text output and json for JSON output.
Enable caching of compiled modules. Experimental.
Disable caching of compiled modules. Experimental.
Run in lenient mode. Experimental.
--truncate-atoms LENGTH
Truncate the length of output atoms.
Add used module and file paths to output. Experimental.
Evaluate values to normal form when profiling timing.

* These options are meant for Fugue developer use only, and are not supported for customer use.


lwc is the compiler for the Ludwig language. It is built to support numerous backend formats and can verify the logical integrity of Ludwig programs at compile time. The Fugue CLI uses lwc to compile compositions prior to creating or updating a process, but lwc can be used on its own to test compilation.

For more information, see The Ludwig Compiler in Writing Ludwig.


Basic compilation of compositions

To quickly compile a composition, use a command like this:

lwc --composition FILE

If no output is returned, the composition has successfully compiled.

Recurring compilation of compositions

If you are working with Ludwig in an editor and on a platform with the watch command (available on macOS via Homebrew), you can get IDE-like fast feedback by running the following command in a separate pane or window on-screen:

watch -n2 -- lwc --composition FILE

A similar behavior can be achieved in bash without watch this way:

while true; do clear; lwc --composition FILE; sleep 2; done

Overriding a binding in a composition

You can use the --set "binding: expr" option to selectively override bindings marked with an @override annotation when the composition is compiled. This allows you to change a value at compile-time without modifying the composition.

Note: You can also override bindings in a composition upon run, update, and policy rbac-attach. In addition, validations should not use @override bindings; details about best practices are available here.

For example, if you have the following composition:

name: "Tommy"

You can add the @override annotation to allow overrides of name:

name: @override "Tommy"

And then you can override name‘s value “Tommy” with the new value “Timmy” by executing the following command:

lwc --set 'name: "Timmy"' Composition.lw

Rules for using overrides

Keep these rules in mind:

  • A binding must include the @override annotation in order to be overridden.
  • You may override multiple bindings by using the --set option more than once.
  • The binding’s type cannot be changed.
  • When overriding a type, include its fully qualified name in the --set argument.
You must include @override

Bindings may only be overridden if they have the @override annotation. Note that bindings are top-level declarations, not fields within records:

# Below, "name" is a binding and may be overridden
name: @override "Tommy"

# Below, "name" is a field in a record, not a binding, and may NOT be overridden:
Person someone:
  name: "Tommy"
  age: 30

# However, you can move the value to a top-level binding and reference it in the record like so:
Person someone2:
  name: someonesName
  age: 30

someonesName: @override "Tommy"

See the Ludwig Syntax Reference to learn about bindings, records, and more.

You may override multiple bindings

When using lwc, you can specify multiple --set arguments to override multiple bindings at once. For example:

lwc --set 'name: "Timmy"' --set 'age: 32' Composition.lw
You can’t change the binding type

The binding’s type may not change. You may substitute a string for a string, a Fugue.AWS.Region value for a Fugue.AWS.Region value, and so on. You may not substitute a Fugue.AWS.EC2.Vpc for a Fugue.AWS.S3.Bucket, or a List<Int> for a List<Bool>.

Use a type’s fully qualified library name in the --set argument

When using the --set option to override a Fugue or custom type, you must use the type’s fully qualified library name – essentially its full path. For example, the fully qualified name of the Us-west-2 constructor is Fugue.AWS.Us-west-2, because that’s its path in the Fugue Standard Library. If you are using custom types and custom libraries, it’s the same principle. If you have a Config folder containing an Environment folder, containing the QA.lw module, containing the Env type, then the fully qualified path for the type is Config.Environment.QA.Env.

So, if you wanted to change a composition’s region to Us-west-2, you’d execute a command like this one:

lwc --set 'myRegion: Fugue.AWS.Us-west-2' Composition.lw

As you can see, we provide the fully qualified name of the Us-west-2 type, which is Fugue.AWS.Us-west-2. Remember that you must always use a type’s full path in the --set option. It doesn’t matter if the composition imports the type with an alias or not, and it doesn’t matter if the composition imported the type explicitly or implicitly. This is because the compiler’s --set option always imports libraries implicitly. For a primer on implicit imports, explicit imports, and fully qualified library names, see Explicit Imports.

Note: Fugue types and custom types require the fully qualified name in the --set argument, but as you saw earlier, primitive types (integers, strings, Boolean values, and others) do not.

A real-world example with a CIDR block

If you want to change a VPC’s CIDR block at compile-time instead of modifying the composition, you could use an @override annotation to do it.

Let’s say this is the original VPC composition:


import Fugue.AWS as AWS
import Fugue.AWS.EC2 as EC2

exampleVpc: {
  cidrBlock: exampleCidrBlock,
  region: AWS.Us-west-2

exampleCidrBlock: ""

You could add @override to the exampleCidrBlock binding, between the colon and the value:


import Fugue.AWS as AWS
import Fugue.AWS.EC2 as EC2

exampleVpc: {
  cidrBlock: exampleCidrBlock,
  region: AWS.Us-west-2

exampleCidrBlock: @override ""

Then, you can call lwc with the --set option. Include the binding you want to change, along with its new value, in quotes:

lwc --set "exampleCidrBlock: ''" MyVpc.lw

If no output is returned, lwc has successfully compiled your composition with the overridden value. But if you want visual confirmation that the values have been overridden, you can use the following command to compile the composition with the simple semantic backend, producing JSON output (which we pipe into jq):

lwc --set "exampleCidrBlock: ''" MyVpc.lw -s simple | jq .

And here is an excerpt of that output, indicating that the CIDR block is now "":

      "tag": "",
      "value": {
        "enable_dns_hostnames": false,
        "enable_dns_support": true,
        "cidr_block": "",
        "region": "Fugue.Core.AWS.Common.Us-west-2",
        "dhcp_options": "&e736595a-0041-57d5-bd0a-b42928b2ae42"
Overriding custom types

The previous example overrides a string. Overriding a Fugue type or a custom type is a similar process. Let’s look at the same composition we started with, and this time we’ll change the region. We’ll create a new top-level binding where we can add the @override annotation:


import Fugue.AWS as AWS
import Fugue.AWS.EC2 as EC2

exampleVpc: {
  cidrBlock: exampleCidrBlock,
  region: exampleRegion

exampleCidrBlock: ""

exampleRegion: @override AWS.Us-west-2

At the command line, we’d call lwc with the --set option to change the region to Fugue.AWS.Us-east-1 (which is the fully qualified module name for Us-east-1):

lwc --set "exampleRegion: Fugue.AWS.Us-east-1" MyVpc.lw

If you compile with the -s simple flag and pipe the results to jq, the output will show that the region has successfully been changed to Us-east-1:

  "tag": "",
  "value": {
    "enable_dns_hostnames": false,
    "enable_dns_support": true,
    "cidr_block": "",
    "region": "Fugue.Core.AWS.Common.Us-east-1",
    "dhcp_options": "&24bdda78-6974-5c67-ac02-fc7b4d394287"