Write a Scenario
This guide walks through the process of creating a CRADLE scenario from an empty .cradle file.
A scenario typically defines:
- scenario metadata
- instances
- networks
- external objects
- lifecycle phases
- events
The exact components required depend on the environment you want to describe.
Create the source file
Create a new file with the .cradle extension.
For example:
ExampleEnvironment.cradle
You can organize scenarios under a dedicated directory:
scenarios/
└── ExampleEnvironment.cradle
The .cradle file is the authoritative source for the scenario.
Generated YAML and backend-specific files should be treated as outputs rather than as replacements for the source.
Plan the scenario
Before writing the source, identify the main components.
For example:
| Component | Example |
|---|---|
| Scenario | ExampleEnvironment |
| Client | Client |
| Router | Router |
| Network | lan_0 |
| Object | InitializationScript |
| Main event | initialize_client |
A simple topology might be:
lan_0DHCP192.168.10.1The scenario will then connect these components through named references.
Define metadata
Start with the metadata() block.
For example:
metadata() >
name("ExampleEnvironment"),
eventType("sequence"),
repositoryRemote("https://172.18.178.10:4443"),
object("InitializationScript").
This:
- names the scenario
- selects the event model
- defines the remote artifact repository
- declares the object used by the scenario
Scenario name
Use:
name("ExampleEnvironment")
Choose a stable name that identifies the scenario.
Event model
Use:
eventType("sequence")
for sequential event handling.
The supported event type is:
sequence
Artifact repository
Use:
repositoryRemote("https://172.18.178.10:4443")
to define the remote artifact base location.
Do not embed usernames, passwords, access tokens or other secrets directly in repository URLs.
Object declarations
Declare objects used by the scenario:
object("InitializationScript")
Each declared object should later have a matching object("name") definition.
For detailed metadata behavior, see Metadata.
Declare instances
Use instances() to declare the systems participating in the scenario.
For example:
instances() >
instance("Client"),
instance("Router").
This introduces:
- Client
- Router
Each declared instance should have a corresponding definition.
Define the instances
Define each instance separately.
For example:
instance("Client") >
os("ubuntu", "20.04"),
object("InitializationScript"),
description("Example client system").
Then define the router:
instance("Router") >
os("ubuntu", "20.04"),
config("linux-router").
The relationship is:
instances()instance("Client")instance("Router")Operating system
Use:
os("ubuntu", "20.04")
to describe the instance operating system.
Configurations
Configurations can be associated with instances using:
config("linux-router")
For example:
instance("Router") >
os("ubuntu", "20.04"),
config("linux-router").
Configuration availability depends on the environment and backend being used.
Objects
Associate an object with an instance using:
object("InitializationScript")
For example:
instance("Client") >
os("ubuntu", "20.04"),
object("InitializationScript").
The object name should match the declaration in metadata().
For detailed instance behavior, see Instances and Roles.
Declare networks
Declare the scenario networks through:
networks() >
network("lan_0").
Each declared network should have a corresponding definition.
Define the network
For example:
network("lan_0") >
subnet("192.168.10.0/24"),
endpoint("Client", "DHCP"),
endpoint("Router", "192.168.10.1").
This defines:
- the
192.168.10.0/24subnet - a DHCP endpoint for
Client - a static endpoint for
Router
The endpoint names reference the instances declared earlier.
Conceptually:
192.168.10.0/24DHCP192.168.10.1Endpoint names must match declared instance names exactly.
Let the backend resolve network details
The subnet can be omitted where supported by the compiler and selected backend.
The Hello World example uses:
network("lan_0") >
endpoint("HelloWorld", "DHCP"),
endpoint("router", "DHCP").
This leaves additional network details to the target-specific generation path.
For more information, see Networks and Routing.
Plan the object definition
Objects describe external artifacts used by the scenario.
For example:
object("InitializationScript") >
location("${uriRemote}/scripts/initialize.sh").
In the complete source, place this final object definition after the event
declarations and definitions. The object name is introduced earlier through
metadata() and its instance association.
${uriRemote} references the remote artifact repository defined by:
repositoryRemote("https://172.18.178.10:4443")
Conceptually:
repositoryRemoteobject-relative pathcomplete artifact locationThe object name should remain consistent across:
metadata()- instance associations
- event references
- the object definition
For detailed object behavior, see Objects and Interpolation.
Define the event lifecycle
Declare the three event lifecycle phases:
events() >
preEvent(),
mainEvent(),
postEvent().
The lifecycle is:
Add an event to a phase
For this example, add one event to the main phase:
mainEvent() >
event("initialize_client").
The event name is initialize_client. It should have a matching event definition.
Define the event
Define event initialize_client:
event("initialize_client") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("InitializationScript", ""),
pauseBeforeRun(0),
pauseAfterRun(0),
scheduleExecution("2026-01-15T10:00:00+00:00"),
description("Initialize the example client").
This event:
- references the
Clientinstance - does not request root privileges
- uses
bash - runs the
InitializationScriptobject - has no delay before execution
- has no delay after execution
- does not wait for another event
- has a scheduled execution time
- includes a description
Add pre-event and post-event behavior
If the scenario requires preparation or follow-up behavior, declare additional events.
For example:
preEvent() >
event("initialize_client").
mainEvent() >
event("collect_evidence").
postEvent() >
event("cleanup").
Each event then receives its own definition:
event("initialize_client") >
...
event("collect_evidence") >
...
event("cleanup") >
...
Semantic event names should be unique within the scenario.
For more information, see Events and Dependencies.
Add event dependencies
CRADLE expresses dependencies through dependsOn().
For example:
mainEvent() >
event("initialize_client"),
event("collect_evidence").
The initialization event has no dependency, so it omits dependsOn():
event("initialize_client") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("InitializationScript", "").
The evidence event can depend on the initialization event:
event("collect_evidence") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("CollectEvidence", ""),
dependsOn("initialize_client").
Conceptually:
Every dependsOn() value must match a defined semantic event name.
Add heuristic annotations
Heuristic annotations can provide additional classification metadata.
For example:
event("initialize_client") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("InitializationScript", ""),
heuristic("ttp", "T1190").
Objects and instances can also use heuristic().
For example:
object("InitializationScript") >
location("${uriRemote}/scripts/initialize.sh"),
heuristic("mbc", "F0002").
Heuristic annotations do not change the underlying behavior of the element.
For more information, see Heuristic Annotations.
Complete example
The resulting scenario is:
metadata() >
name("ExampleEnvironment"),
eventType("sequence"),
repositoryRemote("https://172.18.178.10:4443"),
object("InitializationScript").
instances() >
instance("Client"),
instance("Router").
instance("Client") >
os("ubuntu", "20.04"),
object("InitializationScript"),
description("Example client system").
instance("Router") >
os("ubuntu", "20.04"),
config("linux-router").
networks() >
network("lan_0").
network("lan_0") >
subnet("192.168.10.0/24"),
endpoint("Client", "DHCP"),
endpoint("Router", "192.168.10.1").
events() >
preEvent(),
mainEvent(),
postEvent().
mainEvent() >
event("initialize_client").
event("initialize_client") >
instance("Client"),
needRoot(false),
subject("bash", ""),
runObject("InitializationScript", ""),
pauseBeforeRun(0),
pauseAfterRun(0),
scheduleExecution("2026-01-15T10:00:00+00:00"),
description("Initialize the example client").
object("InitializationScript") >
location("${uriRemote}/scripts/initialize.sh").
Follow the references
The scenario contains several relationships. Read each chain from left to right to trace a name from its declaration to the places where it is used.
Instance references
instances()instance("Client")endpoint("Client", …)instance("Client")Object references
metadata()object("InitializationScript")object("InitializationScript")runObject("InitializationScript", "")Network references
networks()network("lan_0")ClientRouterNames should remain consistent throughout these relationships.
A spelling or capitalization mismatch can leave a named reference unresolved.
Validate the scenario
Save the source as:
scenarios/ExampleEnvironment.cradle
Then validate it:
cxc validate -i scenarios/ExampleEnvironment.cradle
If CradleXC reports syntax errors, correct the source and run validation again.
For more information, see Validate and Compile.
Inspect the scenario
If you want to inspect how CradleXC interpreted the source:
cxc dump-ir -i scenarios/ExampleEnvironment.cradle
This step is optional.
It is mainly useful for inspection and diagnostics.
Compile the scenario
After validation succeeds:
cxc compile \
-i scenarios/ExampleEnvironment.cradle \
-o ExampleEnvironment.yml
The resulting YAML is compiler-generated output.
Continue editing the .cradle file rather than modifying the YAML as the authoritative scenario definition.
Generate target-specific files
If you need output for a particular backend, first confirm that the backend is available:
cxc plugin list
Then generate the target-specific files:
cxc build \
-i scenarios/ExampleEnvironment.cradle \
--target <plugin_name> \
-o ./output
The files produced depend on the selected backend.
CradleXC does not automatically execute backend-generated files. Use them with the corresponding tools in your own testbed environment.
For backend setup, see Use a Backend.
Recommended authoring workflow
A practical scenario-authoring workflow is:
- Plan the instances, networks, objects and events.
- Create the
.cradlefile. - Define scenario metadata.
- Declare and define instances.
- Declare and define networks.
- Confirm that objects are declared in metadata and associated with the intended instances.
- Define the event lifecycle.
- Add events and dependencies.
- Finish with the object definitions.
- Check every named reference.
- Validate the scenario.
- Inspect the intermediate representation if required.
- Compile the scenario.
- Generate target-specific files only when required.
At a high level:
Authoring checklist
Before considering a scenario complete, verify the following.
Scenario and objects
metadata()identifies the scenario.- All objects used by the scenario are declared.
- Every object has a matching definition.
- Object references use consistent names.
- Repository values do not expose credentials.
Instances and networks
- Every instance is declared in
instances(). - Every declared instance has a matching definition.
- Every network is declared in
networks(). - Every declared network has a matching definition.
- Network endpoints reference valid instances.
Events and references
- Lifecycle phases are declared.
- Every event belongs to the intended phase.
- Every event has a matching definition.
- Event instance references resolve.
runObject()references resolve.- Dependency references resolve.
Validation
- The scenario passes
cxc validate.
Keep source and generated files separate
A useful project layout is:
.
├── scenarios/
│ └── ExampleEnvironment.cradle
├── build/
│ └── ExampleEnvironment.yml
└── output/
└── ...
Here:
| Directory | Purpose |
|---|---|
scenarios/ | Human-authored CRADLE source. |
build/ | CradleXC compiler output. |
output/ | Backend-generated target-specific files. |
These directory names are recommendations rather than required CRADLE paths.
Related documentation
- Language Overview
- Syntax and Types
- Metadata
- Instances and Roles
- Networks and Routing
- Events and Dependencies
- Objects and Interpolation
- Heuristic Annotations
- First Scenario
- Validate and Compile
- Use a Backend
Next steps
After writing your scenario, continue with Validate and Compile to verify the source and generate the compiled representation.