All features supporting the SysML v2 BETA specification are currently ALPHA and subject to change. The final SysML 2.0.0 specification is expected to be officially adopted in the second half of 2025. While there have been many questions during the finalization, we believe this implementation provides enough value for early adopters to start preparing to create and execute models using the new specification. We will be expanding support for the specification through finalization, into SysML v2.1, and beyond. There are a small number of remaining issues related to execution scheduled for revision in SysMLv2.1. We will be expanding and clarifying support for SysML v2.x throughout the upcoming language revision process. Consult the What's New and Modeling with SysML v2 pages for more information about what is currently supported.

Delegate Module Providers

A DelegateModuleProvider registers a delegate module with Behavior Execution Engine via a Service Provider Interface (SPI) proxy. This occurs at the start of a Behavior Execution Engine simulation.

You need to include the SPI file in the resulting JAR to load the delegate module prior to running a simulation. To create and include an SPI file, open your project directory that contains the source directory (usually src) and the build.gradle file. From there, create a simple text file in the \resources\META-INF\services\ directory named com.agi.mbse.sysml2.spi.DelegateModuleProvider. That file should contain the fully qualified canonical name of your DelegateModuleProvider for the delegate module. For the example in Figure 1, the name is com.agi.mbse.tutorial.tortoisevshare.TortoiseVsHareDelegateModuleProvider and you can find this SPI file in the sample code provided as a reference in your Behavior Execution Engine installation.

Let's take a look at the contents of TortoiseVsHareDelegateModuleProvider in Figure 1:

package com.agi.mbse.tutorial.tortoisevshare;

import com.agi.mbse.sysml2.api.delegation.*;
import com.agi.mbse.sysml2.spi.DelegateModuleProvider;

import java.util.UUID;

public class TortoiseVsHareDelegateModuleProvider implements DelegateModuleProvider {

    public static final String rootPackageName = "TortoiseVsHare Part 2";

    @Override
    public DelegateModule provideDelegateModule() {
        return new DelegateModule() {
            @Override
            public DelegateIdentity getIdentity() {
                return new DelegateIdentity(
                        UUID.fromString("d547ca5c-3fb7-11f0-ac13-325096b39f47"),
                        "TortoiseVsHare Part 2 Module",
                        "The delegate module for the Tortoise vs Hare part 2 sample."
                );
            }

            @Override
            public void registerDelegate(CustomCodeRegistry codeRegistry) {
                codeRegistry.registerInstanceDataFor(rootPackageName + "::Racer", RacerDataHistory.class, context -> {
                    return new RacerDataHistory();
                });

                codeRegistry.delegateScalarValueForFeature(rootPackageName + "::Racer::distanceCovered", new DistanceCoveredAccessor());

                codeRegistry.delegateExecutionOfAction(rootPackageName + "::Racer::startRacing", context -> {
                    RacerCode.startRacing(context);
                    return DelegationResult.NoAdditionalEvents.INSTANCE;
                });

                codeRegistry.delegateExecutionOfAction(rootPackageName + "::Racer::stopRacing", context -> {
                    RacerCode.stopRacing(context);
                    return DelegationResult.NoAdditionalEvents.INSTANCE;
                });

                codeRegistry.delegateExecutionOfAction(rootPackageName + "::Hare::startNapping", context -> {
                    HareCode.startNapping(context);
                    return DelegationResult.NoAdditionalEvents.INSTANCE;
                });

                codeRegistry.delegateExecutionOfAction(rootPackageName + "::Hare::stopNapping", context -> {
                    HareCode.stopNapping(context);
                    return DelegationResult.NoAdditionalEvents.INSTANCE;
                });
            }
        };
    }
}

Figure 1: The tutorial Tortoise and Hare part 2's delegate module provider

The only method you must implement for a DelegateModuleProvider is provideDelegateModule(), which returns a DelegateModule. A DelegateModule is the plugin point for registering delegates in the system for a simulation.

The DelegateModule has a few different methods that you can override. There are only two that are required for Behavior Execution Engine to use it. The first method is the getIdentity() method, which returns a DelegateIdentity. This identity is used when specifying delegate modules in the model configuration file. Make sure that the UUID that you specify is unique and not reused for any other delegate module. The other required method is the registerDelegate(CustomCodeRegistry codeRegistry) method, which provides a CustomCodeRegistry to make calls to the engine during the pre-simulation initialization of the delegate module.

Registering dependencies

If you want to use other delegate modules as dependencies for your delegate module, you can provide an implementation of the getDependencies() method from the DelegateModule interface, which returns a list of each DelegateDependency. A DelegateDependency is a data structure that takes in an UUID identifier for the required delegate modules.

Behavior Execution Engine will attempt to load dependencies before dependants but will throw an exception if there are any circular dependencies or if a dependency could not be found.

Behavior Execution Engine includes a STK delegate module for loading the JNI libraries into the classpath. To use it in your delegate module, you can specify its UUID bc8b4be5-975f-4084-8fdf-507fc21dfa23 as a dependency. Below is an example delegate module provider that connects to STK.

If you are copying another delegate module, ensure you change the UUID to denote a distinct module.

@Override
public DelegateModule provideDelegateModule() {
    return new DelegateModule() {

        private IAgStkObjectRoot mStkRoot = null;

        @Override
        public List<DelegateDependency> getDependencies() {
            return List.of(
                // This is the UUID for the delegate module: 'com.agi.mbse.stk.StkDelegateModule'.
                // By specifying that as a dependency, the JNI setup of STK will be completed before the load of this module.
                new DelegateDependency(UUID.fromString("bc8b4be5-975f-4084-8fdf-507fc21dfa23"))
            );
        }

        @Override
        public DelegateIdentity getIdentity() {
            return new DelegateIdentity(
                UUID.fromString("3c0096fc-72f7-4e62-8504-48dc7bd0d03f"),
                "MyStkDelegateModule",
                "This is a simple delegate module using stk."
            );
        }

        @Override
        public void initialize(SimulationContext context) {
            // Attempt to grab a window of STK if open.
            try {
                context.getContextLogger().info("Attaching to STK Desktop.");
                mStkRoot = AgStkUi.getStkUiInstance().getIAgStkObjectRoot();
            } catch (Exception ex) {
                context.getContextLogger().info("An open instance of STK was not found.");
                throw ex;
            }
        }

        @Override
        public void registerDelegate(CustomCodeRegistry codeRegistry) {
            /* Registration code */
        }
    };
}

Figure 2: Example delegate module provider template for a delegate module using STK.