We are in early beta, and may not have tested Walnut in your browser/OS combination yet, so it may not look perfect. Thanks for your patience! ×
We are in early beta, and the documentation may be incomplete and/or have missing links. Thanks for your patience! ×

World definition language

The Walnut World definition is a text file defining a virtual simulated world. The file follows all the lexical conventions of the Walnut core languages and uses its elements. It has a sequence of sections in a strict order.

A wold definitions has this syntax

world_definition ::= ("import" STRING_LITERAL "as" NAME)*
                     "state" ":" type
                     ["agents_alternation" "=" expression]
                     ["config" labeled_fields]
                     ["performance" "=" expression]
                     ["end_condition "=" expression]

Each part of this definition will be described in a different section


Walnut has an extension mechanism with allows using components that provides types and functionality related to some specific domain. The Walnut standard library includes a few of these modules and they can be provided by third parties.

To use one of these modules you need to enable it explicitly through an import declaration at the top of your world definition. For each module you need to provide an URL indicating where to load it and a name that will be used to access its functions.

Valid module URLs are:

  • std://path, where path is any string refers to standard Walnut modules a compelte list of the modules is available in a later section
  • lib://path, where path is a slash separated list of names, refers to third party modules that has been installed. A later section describe how to build these modules (which are programmed in Python) and how to install them

A module can provide a set of implicit type declarations that can be used as if they were included in your world definition. The module can also provide functions that will be available to be called in expressions using modname.funname(args, ...) where modname is the name specified in the import declaration and funname the name of one of the functions provided by the module. Finally, a module can provide several procedures that can be used in actuator statements, using similar syntax.

Type Declarations

A world definition can include any number of type declarations. These types will be available throughout the rest of the world definition, and any constructor defined will be usable in problems and visualizations related to this world.

World State

The world definition will define a single type to be used to the global state. This must be the type of the initial state (defined in the problem), and any update to the global state made by the actuators must set a value of this type.


Each world defines at least one agent role. The problem definition will map agents to roles. Each role definition follows the syntax

role_definition ::= "role" T_LABEL ["(" labeled_fields ")"]
                    "sensor" "=" expression
                    "action" ":" type

role_actuator ::= "actuator_for" pattern ["valid_when" expression] "do" block

Each role must have a unique label which will be used to refer to the role in the problem definition.

Optionally a role can define several fields and their types. This information will be part of the agent state for agents of this role. Note that during the simulation the values of these fields will be per agent, not per role.

The sensor expression is evaluated on every time step where an agent with this role takes a perception. The expression is evaluated in an [agent context]_ for the agent taking the perception. There is no restriction to the resulting type of this expression; however the agent program must be designed to handle the result values. If the sensor fails to evaluate the simulation is stopped. Otherwise it is sent to the agent program.

The role definition must specify the type of the actions that will be sent by the agent programs for this role to the simulation, on action time steps.

Each role must define at least one actuator. Each actuator has one pattern which is used to decide which actuator(s) apply to the action. Each actuator has a validity expression (which defaults to True if not present), and a block of statements that modify the global state and/or the state of the agents (statements are described in another section).

On each time step for an agent, the simulator waits for the agent program to send an action. When the action is received, the action is checked against the action type for the agent's role. If there is a type mismatch, the simulation is stopped with an error (a faulty agent is assumed). Otherwise, the action is matched with each actuator pattern in the order defined in the role. For each successful pattern match, an [agent context]_ is created and updated by the pattern, and the validation expression is evaluated in that context. If the validation expression fails to evaluate or evaluates to anything but True the agent is considered faulty and the simulation stopped. Otherwise the block of statements is executed (also in the pattern enriched agent context). Note that it is possible to multiple actuators to match, which will result in many blocks to be executed, always in top-to-bottom order as specified in the role definition.

Agents Alternation

The agents_alternation expression is evaluated in a [standard context]_. It must evaluate to a list of strings, and each of the strings must be one of the identifiers for one of the agents as specified in the problem.

The expression is evaluated at the start of the simulation. Each time step the first element of the list is checked:

  • A value of id, makes this time step a perception+action of the agent with id, and the element of the list is removed
  • An empty list forces a reevaluation of the expression and the cycle restarts

If the evaluation of the expression fails or returns an invalid value, the simulation is aborted.

The agents_alternation expression is optional. When omitted, it defaults to agent_ids which is the list of agent ids, as stated in the problem description.


The config section, if present, allows describing the names and types of several fields. These fields will require specific values in the problem definition, and the provided values should match the type.

The fields defined here are available as keys in the config variable of a [standard context]_, so the values set by the problem will be accessible from expressions and statements.

Performance definition

The world can optionally include a performance function which is evaluated for each agent on each time step, within an agent context. If the performance function is not included, then the problem must define one to be able to run a simulation.

The performance function should evaluate to a Number. If it evaluates to something else or fails to evaluate, the simulation is stopped with an error.

End condition

The world can optionally include an each condition which is evaluated once per time step within a standard context. If the end condition function is not included, then the problem must define one to be able to run a simulation.

The end condition function should evaluate to a Boolean. If it evaluates to something else or fails to evaluate, the simulation is stopped with an error. If it evaluates to True the simulation is considered finished normally. If it evaluates to False, the simulation continues.

Context variables

All expressions are evaluated having some useful information defined in the context (i.e., some variables with have values set that you can use in the expression)

A standard context

  • state: the global state. The type is given by the global state type in the world definition
  • agents: a dictionary in which each key is an identifier of an agent, and the value is the internal state (custom value, according to the role) of that agent. The constructor label is the role name, and the fields are the state variables defined for the role
  • agent_ids: a list of all the agent identifiers. The list is ordered in the same order as the agents were defined in the problem.
  • config: a Dict {?} containing all the configuration values specified in the problem. Its keys are those defined in the config section plus any additional ones specified in the problem
  • time: contains the current count of events (the current time step)
  • performances: a Dict {Number} mapping agents names to their last measured performances.

Standard contexts are used to evaluate the end condition and agents alternation expressions.

An agent context extends a standard context for a specific agent adding the following variables:

  • agent_id: the id of the current agent
  • agent: the state of the current agent (same as agents[agent_id])
  • last_performance: the last measured performance for the current agent

Agent contexts are used to evaluate sensors, actuators, and performance for an agent.


Statements are instructions that allow describing modifications to the state. Walnut provides a simple imperative language to modify the state which is not very powerful (no looping, no recursion). If you want to describe more complex manipulations of the state you should do them from custom procedures in extension modules.

Statements are only used in actuator definitions and follow the same syntax

block ::= "{" statement* "}"
statement ::= NAME trailing* "=" expression
         |    "if" expression "then" block ["else" block]
         |    NAME "(" [call_arguments] ")"
         |    NAME "." NAME "(" [call_arguments] ")"

A block is just a sequence of statements which are executed from first to last.

Each statement is an assignment, a conditional, or a procedure call.

Assignments modify or create variables in the context. This can mean updates to the global state (state), the current agent state (agent), or the collection of agent states (agents). At the left of the = there is a specification of which context variable, or which of their indices/attributes is updated. At the right there is an expression which is evaluated to set the selected part of the context. Context changes that do not modify the simulation state (for example, setting x = 0) are visible in the following statements of the same actuator. A failure to evaluate the expression results in a stopped simulation.

A conditional statement evaluates the initial expression. If the evaluation fails or returns a non-boolean, the simulation is stopped. If the evaluation results in True, the then block is executed; if the result is False and an else block is present, then that block is executed instead.

A procedure call invokes code external to the world definition (part of the simulator of some external library). The procedure receives labeled or unlabeled arguments (each is an expression that must evaluate successfully), and may have side effects on the context and/or modify the values it receives as arguments. the procedure call can fail if the argument count is wrong, if arguments are of the incorrect types or invalid values. Each procedure documentation specifies which argumetns are values and what are the expected effects.

Walnut provides a set of built-in procedures documented below. In addition, each module can provide additional procedures which are called with module_name.procedure_name(arguments), where module_name is the name given to the module in the import declaration


The following is a list of Walnut built-in procedures, grouped by category with a summary. After this listing there is a full specification of each function, in alphabetical order.

Dictionary operations:
  • clear: Deletes all keys from a dictionary
  • del: Deletes a given key from a dictionary
  • update: Updates multiple keys in a dictionary
List operations:
  • append: Add an item at the end of a list
  • prepend: Add an item at the beginning of a list
  • remove: Finds an remove an occurrence of a value
  • splice: removes/inserts/replace a list onto another.


Type: append([T], T)

Modifies a list adding a new element at the end. The list should be accessible from the context, otherwise the result will be lost. For example

    x = [2, 1, 5]
    append(x, 3)
    # Now x is [2, 1, 5, 3]

    append([1, 2], 3)
    # The list that was created and changed in the line above is
    # imposible to reference here, so it is lost.