MARI is an open-source framework for building robust, scalable and distributed 'multi-agent like' system based on Microsoft Orleans.
M-A-R-I (ex democrite from nexai.net) offers an automated orchestration and configuration system, enabling dynamic creation, editing, and modification of grain interactions.
It incorporates built-in Features to manage virtual grains and facilitate communication between them. This simplifies the creation of various Sequences of virtual grains that can be transformed an input to output. M-A-R-I utilizes Signals to transmit information and Triggers to initiate different Sequences, thereby chaining processes through a graph. Most Features are describe in a serializable structure.
M-A-R-I functions as a layer on top of Microsoft Orleans, ensuring that all features of Orleans are retained.
- Scalability using silo as virtually one server
- Robustes through virtual actor model
- Simplicity by grain design
All configurations possible with Orleans are still available, but M-A-R-I offers a simplified, fluent configuration model.
- Nodes : Backend part where all the VGrain live to solve requests.
- Client : Proxy used to send query to cluter's node.
- Cluster : Group of client and nodes to distribute work allong multiple devices.
- Storages : Define where and how the data are persisted.
- Virtual Grains (vgrain) : M-A-R-I extention of orleans grain.
- Virtual Grains Id : M-A-R-I dynamic usage of id and templating.
- Sequences : Linear transformer that chain VGrain to treat a input data and provide an output from it.
- Signals : Information send through the cluster that could be used as stimulus. (Trigger, Services ...)
- Triggers : Connectors designed to initiate reactions based on a stimulus. (Signal, Door, Cron, Stream Queue, ...)
- Doors : Connectors designed to initiate reactions based on stimulus that follow specific conditions.
- External Code VGrain : VGrain write in other technologie than .net, like Python, C++, Javascript, ...
- Blackboard : Technologie of shared memory with intelligent controller. Use to organize and process a specific task, goal.
- Redirections : Enable switch of implementations for a particular contract during runtime.
- Dynamic Definitions : Allow definition to be generated and inject at runtime.
- Toolbox : Shared technical library (models, extensions, serialization, patterns, ...) used by the whole framework.
Tip
In green this is the lastest feature integrated
A node refers to a server within a cluster.
It is recommended to establish a client (server API) along with multiple nodes that process requests.
This approach enables scaling of the processing component while maintaining a simplified facade.
M-A-R-I node has an individual setup.
var node = MARINode.Create((ctx, configBuilder) => configBuilder.AddJsonFile("appsettings.json", false),
cfg =>
{
cfg.WizardConfig()
.SetupClusterFromConfig()
.ConfigureLogging(c => c.AddConsole());
...
});
await using (node)
{
await node.StartUntilEndAsync();
}Tip
Next Incomming: IHostBuilder integration to allow configuration on existing application.
A client is a separate program that utilizes the cluster's capabilities.
It requires configuration to locate the cluster and send requests.
It is advisable to set up the client and the cluster's nodes on different machines.
M-A-R-I client could be setup individually or through existing application setup.
Individually
var node = MARIClient.Create((ctx, configBuilder) => configBuilder.AddJsonFile("appsettings.json", false),
cfg =>
{
cfg.WizardConfig()
.SetupClusterFromConfig()
.ConfigureLogging(c => c.AddConsole());
...
});
await using (node)
{
await node.StartUntilEndAsync();
}Integrated
// Add mari client
builder.Host.UseMARIClient(cfg => { ... });Like Orleans, M-A-R-I is build to work in cluster. Client and nodes communicates to each other to solve request.
This solution provide a strong resilience because if one node falldown automatically his work is relocate to other nodes.
To build a cluster you need a way for each new node to discovert the other and share information like status or workload.
Different strategy exist, Orleans choose the database meeting point.
When you work in local you just need to configure using (NoCluster) :
var node = MARINode.Create(cfg =>
{
cfg.WizardConfig()
.NoCluster() // -> Setup the system to only bind to 127.0.0.1 and allow only local clientBy default, in opposed of Orleans way, the local configuration allow multiple local nodes to form a cluster.
I you want to prevent multiple nodes you have to add the following configuration:
var node = MARINode.Create(cfg =>
{
cfg.WizardConfig()
.NoCluster() // -> Setup the system to only bind to 127.0.0.1 and allow only local client
.AddEndpointOptions(new ClusterNodeEndPointOptions(loopback: true, siloPort:50000))Nuget package : MARI.Framework.Extensions.Mongo
You can use mongo db as a Meeting point.
To do so you just have configure :
var node = MARINode.Create(cfg =>
{
cfg.WizardConfig()
// Setup mongo db as cluster meeting point
.UseMongoCluster(m => m.ConnectionString("127.0.0.1:27017"))Same go for the client part
builder.Host.UseMARIClient(b =>
{
b.WizardConfig()
.UseMongoCluster(m => m.ConnectionString("127.0.0.1:27017"))Important
Attention by default a mari node doesn't allow client to connect to it. (Except in NoCluster configuration use to dev)
it's a security to prevent any node to be consumed by client.
To enabled the node to be open to client you have two choose:
- Add manually a gateway port : .AddEndpointOptions(new ClusterNodeEndPointOptions(gatewayPort: 4242)) // It will conflict if you have multiple node in local
- Let system choose a free port as gateway : .AddEndpointOptions(new ClusterNodeEndPointOptions(autoGatewayPort: true))
See the full usage in sample ExternalStorages/MongoDB
A M-A-R-I cluster need some information to be stored:
- Cluster information (cf. Cluster)
- VGrain State
- Reminder clock information
- Definitions (Sequences, triggers, signal, ...)
- Custom Data
When you write a VGrain you can inherite from VGrainBase{TVGrainInterface} or VGrainBase{TSTATE, TVGrainInterface}.
The second one provide a State management for you. It means the state will be load/create when the VGrain is activate and store when it is desactivate.
Tip
In case of crash, the state storage could not be garanty, to prevent this you can call in you code the method "PushStateAsync"
The issue with that is the time consumtion
To store the state you need to provide through the constructor a IPersistentState{TState}.
The good practice is to get this instance from dependency injection with tag.
public CounterVGrain(ILogger<ICounterVGrain> logger,
[PersistentState("Counter")] IPersistentState<CounterState> persistentState) In this example we request a storage place to store a CounterState object.
Using the attribute PersistentStateAttribute we customize "How" it will be stored.
First parameter is the Storage name (for example the table or collection) and you can specify a second parameter as the Storage configuration Key.
Tip
You can request by constructor as many storage as you want.
It's an easy way to managed the data storage
Nuget package : MARI.Framework.Extensions.Mongo
You can use the mongo extension to provide storage for all the components:
-
Cluster information -> cf. Cluster
-
Definitions (Sequences, triggers, signal, ...)
// Define mongo db as definition source
.AddMongoDefinitionProvider(o => o.ConnectionString("127.0.0.1:27017"))- State/Custom storage (VGrain State, Reminder clock information, Custom Data)
// Setup mongo db as default storage
.SetupNodeMemories(m =>
{
// Enum StorageTypeEnum provide all sub type
// You can customize the database name, the connection string ...
// By default it will reuse the LAST connection string configured
m.UseMongo(StorageTypeEnum.All);
});See the full usage in sample ExternalStorages/MongoDB
In accordance with Orleans terminology, a grain is a virtual actor that can appear in any compatible silo, with its state being restored if necessary.
In Orleans, to invoke a grain, one must request a proxy instance from the IGrainFactory. This proxy seamlessly manages the communication between the caller and the called.
This is why a grain consists of an interface and an implementation, allowing the proxy to inherit the interface.
With M-A-R-I, there is no need to explicitly call the grain yourself, it will do it for you based on the configuration.
This is the reason we refer to them as Virtual Grains (VGrain), to denote a behavior that prevent direct call consumption.
A Sequence is a series of virtual grains executed sequentially, where the output of one can be used as input for the next VGrain.
The aim is to set up this sequence description just once and save it in a database. To run the sequence, only its unique identifier (Uid) is required.
Important
Currently, only local declarations are supported. Please refer to the Next section for information on future capabilities to load these configurations from a storage system, such as databases.
To configure and test sequences you need to create and register it in the MARINode configuration.
Important
Every definition (sequence, trigger, signal, door, stream queue, artifact, ...) is identified by a SNI (Simple Name Identifier) used to build its RefId.
A SNI must match [a-z0-9_-]{2,}: lower case letters, digits, _ and - only (ex: forex-collector), otherwise the build throws an InvalidDataException.
Use the optional displayName parameter for a human friendly name (ex: Sequence.Build("forex-collector", "Forex collector")).
Build definition
var collectorSequence = Sequence.Build("forex-collector", "Forex collector")
// Ask a web URI in input
.RequiredInput<Uri>()
// Fetch html page and return it
.Use<IHtmlCollectorVGrain>().Call((a, url, ctx) => a.FetchPageAsync(url, ctx)).Return
// Configure inspector on specific pair inspect and extract current value
.Use<IPriceInspectorVGrain>().Configure(currencyPair)
.Call((a, page, ctx) => a.SearchValueAsync(page, ctx)).Return
// Store the value received into a dedicated statefull grain
.Use<ICurrencyPairVGrain>().Configure(currencyPair)
.Call((a, data, ctx) => a.StoreAsync(data, ctx)).Return
.Build();Register definition
var node = MARINode.Create((ctx, configBuilder) => configBuilder.AddJsonFile("appsettings.json", false),
cfg =>
{
cfg.WizardConfig()
.NoCluster()
.ConfigureLogging(c => c.AddConsole())
.AddInMemoryDefinitionProvider(m =>
{
// Local in node memory setup
m.SetupSequences(collectorSequence);
})A Sequences can be executed manually, but it can also be triggered automatically.
There are differents kind of triggers :
- Time Periodicity, use a cron expression to define the periodicity
- Signals, trigge when configured signal is also fire
Similar to sequences, trigger definitions can currently be created and stored locally, with plans for future storage in external sources like databases.
Important
Currently, only local declarations are handled. The upcoming goal, detailed in the Next section, is to enable loading these configurations from a storage source.
A trigger can supply an input to initiate the sequence.
Important
Currently, only static data collection is supported. Please see the Next section for information on the future goal of loading these configurations from an external provider.
Time Periodicity
// Every minutes between 9h and 18h UTC between monday and friday
var triggerDefinition = Trigger.Cron("* 9-18 * * mon-fri", "forex-collector-tick", "Forex collector tick")
// Define how to get input information that will be send to targets
// (the output must be set BEFORE the targets)
.SetOutput(input => input.StaticCollection(collectionsources)
.PullMode(PullModeEnum.Circling))
// Define what will be trigged (Sequence, signals or streams)
.AddTargetSequence(collectorSequence)
// You could have many target or many types
//.AddTargetSequence(collectorSequence2)
//.AddTargetSignal(signalId)
.Build();Signals
// listen inputSignal and trigger when this one is fire
var signalTriggerDefinition = Trigger.Signal(inputSignal, "on-input-signal")
.SetOutput(input => input.StaticCollection(collectionsources)
.PullMode(PullModeEnum.Circling))
// Define what will be trigged
.AddTargetSequence(collectorSequence)
.Build();Register definition
var node = MARINode.Create((ctx, configBuilder) => configBuilder.AddJsonFile("appsettings.json", false),
cfg =>
{
cfg.WizardConfig()
.NoCluster()
.ConfigureLogging(c => c.AddConsole())
.AddInMemoryDefinitionProvider(m =>
{
// Local in node memory setup
m.SetupTriggers(signalTriggerDefinition);
})Use an EBS (Entreprise Bus Service) as storage an diffuseur of job to to.
Orlean can nativaly used different type of ESB.
With M-A-R-I we create connector through trigger to push and pull.
Send to stream :
// PUSH
var trigger = Trigger.Cron("*/25 * * * * *", "push-every-25-sec", "TGR: Push Every 25 sec")
.SetOutput(s => s.StaticCollection(Enumerable.Range(0, 50))
.PullMode(PullModeEnum.Broadcast))
.AddTargetStream(streamDef) // <----- Add stream as target
.Build();Consume from stream :
// PULL
var fromStreamTrigger = Trigger.Stream(streamDef, "from-stream") // <----- Trigger that fire until theire is a message in the stream queue
// Limit the number of concurrent execution, Prevent consuming all messages without resources in the cluster to process them (CPU, RAM, ...)
.MaxConcurrentProcess(2)
.AddTargetSequence(consumeSeq.Uid)
.Build();The signals feature consists of two components:
- Signal
- Door
A signal functions similarly to an event, but with a "fire and forget" approach.
By default, the signal includes:
- Definition name & Uid
- The VGrain information that fire
- The datetime when it is fire
- The possible previous signal that cause this one to fire
However, it can carry a small amount of information.
It is recommended to keep this information as minimal as possible to avoid memory issues.
It could be something as simple as an ID referencing data in storage.
Define a signal:
var signalA = Signal.Create("signal-a", "Signal A");A Door can listen to multiple signals and, based on specific conditions, can emit its own signal.
Currently, a boolean logic door is available, but you can easily create and configure your own gate logic.
Define a Logic boolean door:
var door = Door.Create("check-pair-above-average", "CheckPairAboveAverage")
.Listen(valueEurUsdStoredAboveAverage, valueEurChfStoredAboveAverage)
// Basic
// Fire If (A & B) are fired in a 10 sec window
// By default the door unlock as soon as the condition is valid and
// signal activation are only use one.
.UseLogicalAggregator(LogicEnum.And, TimeSpan.FromSeconds(10))
// Advanced
//.UseLogicalAggregator(b =>
//{
// return b.ActiveWindowInterval(TimeSpan.FromSeconds(0.5))
// .AssignVariableName("A", valueEurUsdStoredAboveAverage)
// .AssignVariableName("B", valueEurChfStoredAboveAverage)
// .AssignVariableName("C", manualForceDoorFireing)
// /* Fire (if A and B are signal in an interval of 0.5 second except if i was already fire in less than 0.5 seconds)
// Or
// C
// */
// .Formula("(A & B & !this) | C");
//})
.Build();- LogicalAggregator : Apply a boolean condition based on signal activation (1 if activate on period of time, otherwise 0)
- RelayFilterDoor: Apply a condition a the signal structure itself, use a filter to trigger specific sequence
Register definition
var node = MARINode.Create((ctx, configBuilder) => configBuilder.AddJsonFile("appsettings.json", false),
cfg =>
{
cfg.WizardConfig()
.NoCluster()
.ConfigureLogging(c => c.AddConsole())
.AddInMemoryDefinitionProvider(m =>
{
// Local in node memory setup
m.SetupSignals(t => t.Register(signalA))
.SetupDoors(t => t.Register(door));
})In the Orleans framework, a grain definition can have multiple virtual instances.
However, only one instance is active at any given time, associated with a unique identifier known as a GrainId.
In Orleans, it is the user's responsibility to supply the correct GrainId of the grain they wish to call.
In M-A-R-I, virtual grains are instantiated and called by a generic orchestrator.
By default, a new Guid is used each time, which is ideal for stateless grain.
Virutal Grain interface could be tag by attribute VGrainIdFormatAttribute to indicate how to build the GrainId.
The template ID system provides the ability to dynamically create a GrainId using data input or execution context as the source of information.
You can see a good example in the sample Forex.
This one use stateless virtual grain (vgrain) to download html page and parse it
but use a statefull virtual grain (vgrain) to store the value extracted.
This virtual grain (vgrain) employs a string value, such as a forex pair (eur-usd, eur-chf, etc.), from the execution context to form its GrainId, resulting in the creation of a single reusable instance for each pair
This allow :
- A client to directly call this vgrain to extract the values.
- To create only one grain by pair that is single-thread handled by orleans (no need to think of concurrent access)
- Store information in dedicate a serializable model in class and not to focus on the storage mode (databases, files, ...)
Tip
You can access those grain usign the classic orleans IGrainFactory way.
BUT it is better to use IMARIExecutionHandler who will use correctly and automatically the correct GrainId.
With M-A-R-I we can use other satellite program or script to perform jobs.
To do so you need simple steps:
- Create Code artifact definitions (
Artifact.VGrain("my-script", "My script")..., the first parameter is a SNI) - Using through a vgrain
- Use generic vgrain in the library IGenericArtifactExecutableVGrain
- Create your own vgrain by inherite from ArtifactExecutableBaseVGrain<> instead of VGrainBase
A python package exist to handle the communication protocol with mari README.md
You can found a full sample Here
A blackboard is a temporary shared space with specific controllers to solve a problem.
The goal is to group specific analyze result and have controllers deciding the next step to acheived to a solution.
You can found a full sample Here
M-A-R-I has devised a dynamic solution for determining the implementation to be utilized for a contract.
There are several reasons why multiple implementations might be necessary.
Now, with the introduction of a feature called "Redirection" you can dynamically alter the implementation used for a specific contract.
It is usefull for:
- Testing: Ensure that implementation won't break existing sequences
- Competition: You can them make implementation in competition to select the best related to context.
- ...
This redirection could be setup a different level:
- Call Scope: During a call using IMARIExucutorHanlder. Limit the redirection only to a specific call, you can even specialize to a specific stage.
- Global Scope: Available on all the cluster
You can found a full sample Here
M-A-R-I serves as an orchestrator for vgrains, utilizing serializable definitions as work orders.
These definitions are primarily formulated and stored in an external system such as databases.
The objective of the "Dynamic Definitions" feature is to generate definitions (sequences, triggers, etc.) at runtime, with a lifecycle linked to storage.
If no specific storage is provided, the definition will exist only for the duration of the cluster's lifetime.
This capability enables dynamic testing of new sequences, creation of temporary triggers, and activation of temporary debugging features, among other functionalities.
You can found a full sample Here
MARI.Toolbox is the technical library shared by all M-A-R-I projects: models (concrete types, method descriptions, ...), extensions, serialization helpers (Newtonsoft converters, type solver), disposables, patterns (strategy, workers), expressions, conditions, services (hash, file system, globalization), ...
Note
The toolbox was previously published as the separate packages Elvex.Toolbox (Elvex.Toolbox, Elvex.Toolbox.Abstractions, Elvex.Toolbox.Configuration, Elvex.Toolbox.UnitTests.ToolKit).
It is now part of this repository in src/Toolbox, renamed MARI.Toolbox, and referenced by the framework projects as project references.
To migrate your code replace the namespace Elvex.Toolbox by MARI.Toolbox (cf. Release Notes).
| Project | Content |
|---|---|
| MARI.Toolbox.Abstractions | Interfaces, models and attributes |
| MARI.Toolbox | Implementations (extensions, serialization, services, patterns, ...) |
| MARI.Toolbox.Configuration | Configuration helpers |
| MARI.Toolbox.WPF, .WPF.Abstractions, .WPF.UI, .WPF.UI.Avalon | WPF tools and controls (Windows only) |
| tests/MARI.Toolbox.UnitTests.ToolKit | Unit test helpers (xunit, AutoFixture, NFluent) |
- MARI.Framework.Node: Reference this one by your node project.
- MARI.Framework.Client: Reference this one by your client project.
- MARI.Framework.Builders: Reference this one by your project that build definitions.
If you split the agent implementation and definition in separate projet you could only reference the nuget package MARI.Framework.Core
Framework Feature
- MARI.Framework.Node.Cron: Reference this one by your node project to enable the cron mechanism.
- MARI.Framework.Node.Signals: Reference this one by your node project to enable the signals mechanism.
- MARI.Framework.Node.StreamQueue: Reference this one by your node project to enable the stream mechanism.
- MARI.Framework.Node.Blackboard: Reference this one by your node project to enable the blackboard mechanism.
Toolbox (automatically referenced by the framework)
- MARI.Toolbox / MARI.Toolbox.Abstractions: Technical library shared by the framework (cf. Toolbox).
- MARI.Toolbox.Configuration: Configuration helpers.
Extensions
- MARI.Framework.Extensions.Mongo: Reference this one by your node project to enable the mongo db Storage.
- MARI.Framework.Extensions.Docker: Reference this one by your node project to execute artifacts in docker containers.
Bags
- MARI.Framework.Bag.DebugTools: Reference this one by your node project to enable debug sequences or VGrain (Like Display, ...).
- MARI.Framework.Bag.Toolbox: Reference this one by your node project to enable basic tools sequences or VGrain (like delay, ...).
To create a node you just have to follow the example bellow.
Caution
Orleans scan by default all the project dll. Due to .net assembly load behavior if you deport your agent implementation in another projet is may not be loaded if you don't directly use any of the type defined. Reference the project is not enough. In the Next section you will see an objectif to reference assembly to load for now you have to use the SetupNodeVGrains method in the wizard configurator.
In Program.cs:
var node = MARINode.Create((ctx, configBuilder) => configBuilder.AddJsonFile("appsettings.json", false),
cfg =>
{
cfg.WizardConfig()
.SetupClusterFromConfig()
.ConfigureLogging(logging => logging.AddConsole());
...
});
await using (node)
{
await node.StartUntilEndAsync();
}To create a client you just have to follow the example bellow.
Caution
All nodes and clients need a meeting point to know the others and form a cluster, orleans choose the database strategy. By default only one node and one client could be present on the same machine wihtout any db setup. But You could use the orleans fluent method to configure your cluster and client. You can now use different database solution for all storage type look for #Cluster section
In Program.cs:
var node = MARIClient.Create((ctx, configBuilder) => configBuilder.AddJsonFile("appsettings.json", false),
cfg =>
{
cfg.WizardConfig()
.SetupClusterFromConfig()
.ConfigureLogging(logging => logging.AddConsole());
...
});
await using (node)
{
await node.StartUntilEndAsync();
}To execute a sequence or call a specific grain you have to use the service IMARIExecutionHandler.
This handler follow mari rules in grain id generation.
- Normalize your data model and create small Virtual Grain with small sponsability.
- Follow the SRP (Single Repsonsability Principle) as describe in the S.O.L.I.D pattern
- If you attach information to signal use small one like simple id.
- Prefer mari configuration if possible to prevent any side effect non managed
Note
The samples reference the framework sources of this repository (project references to src) and not the published nuget packages.
Each sample has its own solution in samples, build and run it directly (ex: dotnet run --project samples/Streams/Stream.Console).
In the section Sample/Forex
Use case
Fetch reguraly a forex pair value using public web site, store the values and be able to consume them through an api.
Warning
The sample scraps investing.com that currently answers 403 (Forbidden) to bots: the values stored stay empty (null).
Features Use
- M-A-R-I sequence definition
- M-A-R-I Cron Trigger definition
- M-A-R-I automatic Virtual Grain Id
- Orleans persistant state
- Minimal .net API
- M-A-R-I client IMARIHandler usage
Use case
Create a node cluster with a client connected through external storage. In storage we will also store definitions of a trigger that will every minute increment a counter tag with a name. Through the client you can look for the counter value through a swagger api
Features Use
- M-A-R-I sequence definition
- M-A-R-I Cron Trigger definition
- M-A-R-I automatic Virtual Grain Id
- Storage:
- Cluster information (client/node)
- Definitions
- Reminder informations
- Orleans persistant state
- Minimal .net API
- M-A-R-I client IMARIHandler usage
Mongo
In the section samples/ExternalStorages/MongoDB
In the section Sample/RelayFilterDoor
Use case
Relay filter door can apply a condition on signal received. For example if the signal transport a value of type int, let pass.
- Trigger dedicated sequence if function of the data carry
- Trigger dedicated storage based to the type of data carry
Features Use
- M-A-R-I RelayFilterDoor definition
In the section Sample/PythonVGrains
Use case
Use Python scripts inside mari environment like VGrain standard
Caution
In version 0.2.2 only local python script deployed with the mari node as 'Content' is supported
You goal is to be able to support different package provider,
in different format late on. (From api, zip, shared folder, resource embedded ...)
- Use RNN (Neural Network) library in python to solve data
- Re-use you librairies
Features Use
- M-A-R-I Artifact Definition, Packaging
- External code executor (allow mari to used external program as solver)
In the section Sample/Blackboard
Use case
Use a Blackboard as central point to group information related to a specific case and compute them when needed.
We demonstrate with a simple calculator. We can store in the blackboard different type of numeriacl value and sum them when a limit is reach or manually.
- Process Different modality (Text, Video, Image) in a same place and use dedicated controller to perform advance analyze, remove useless data, archive some others ...
- Train an IA by storing good data set and rejet others, detect automatically when quality response is reach
Features Use
-
Blackboard Storage
- Rules
- Type check
- Multi-Storage
-
Blackboard Controller
- Storage : Responsable of the data integrity
- Event : Responsable to compute when condition are fullfill.
In the section Sample/Streams
Use case
Use a Stream as data input for mari activity through a specific trigger.
- Buffer a lot of work in a StreamQueue
- Communicate with external system in and out
Features Use
- Stream source trigger
In the section Sample/Redirections
Use case
- Wanted to change the behavior of some implementation whitout impacting existing.
- Be able to put in competition multiple implementation an see the best result
Features Use
- Grain Implemation redirection
- Local during a call
- Global for all the cluster
In the section Sample/DynamicDefinition
Use case
- A blackboard need some data processing, it can generate it's own sequence and signal definition at runtime
- You want to test a VGrain in some condition you can create you sequence using it
- You pilote IA want to test different algorithm variation
Features Use
- Dynamic definition
- Injection
- Activation/Deactivation
- Suppression
v 1.0.0:
Release Node
- Integrate the toolbox in the repository (Elvex.Toolbox -> MARI.Toolbox)
- Restore the Artifacts sources
- Samples use the framework sources (project references)
- Bug fixing
v 0.5.0-prerelease:
Release Node
- Stabilisation
- Bug fixing
- Repository Architecture optimization
- Auto-Configuration through json file
- Target mari resources throught RefId
- Create mari throught Yaml compilation
v 0.4.4-prerelease:
- Stabilisation
- Bug fixing
- Official support of Amexio Group to the projet
v 0.4.3-prerelease:
Release Node
- Blackboard
- Query
- Life Status
- Signals
- Deferred Query Response
- Signal - Hierarchy
- Sequence : improve input usage
- Execution Handler
- Signal at the end
- Deferred
- Meta-Data -- Descriptor
- Artifact - Enviroment - Docker
v 0.4.1-prerelease:
Release Node
- Migrate to .net 8
- Force grain redirection for a precise sequence execution
- Call sequence from sequence
- Create sequence definition in runtime
v 0.3.0-prerelease:
Release Node
- Blackboard extensions
- Create bags as container of generic (toolbox, debug)
- Repository : global & simple storage system not linked to a grain state
- Process through a foreach a sub properties collection in a sequence
- Trigger use to push or pull data from a stream
- Fire signal from sequence
v 0.2.2-prerelease:
Release Node
- Fully integrate Python VGrain in M-A-R-I environment
v 0.2.1-prerelease:
Release Note
- Easy cluster external storage to store virtual grain state, reminder, membership ...
- IHostBuilder integration to allow configuration on existing application.
- Load Definition, sequence, signals, triggers, ... from an external source like databases using the design pattern strategy through IProviderSource
| M-A-R-I Version | Minimal .net version | Minimal orlean version |
|---|---|---|
| 1.0.0 | .net 8.0 | 8.0.0 |
| 0.3.0 | .net 7.0 | 7.2.4 |
| 0.2.1 | .net 7.0 | 7.0.3 |
In 2017, while leading the AI Lab at Sopra Steria in Brussels, my friend Mickael Thumerel and I built the first version of Democrite, a multi-agent framework—my specialty. By 2022, we decided to rebuild it entirely from scratch. We initially planned to launch our own company, but eventually, our journey led us to join the Amexio group.
On December 22, 2024, Mickael passed away after a sudden, tragic illness. Six months later, I stepped down from my role at Amexio.
It took me two years to get my head back into Democrite. Mickael was by far the better developer between us, but with the help of Opus 5.5, I’ve finally reclaimed the codebase and made it my own again.
Nexai.net was our shared dream. I’ve shed a few quiet tears while coding it, but Nexai.net isn't just a project—it's a continuation of what we built together.
I miss you, Micka. I love you, my friend.
- Mickael Thumerel died on December 22, 2024 at the age of 35. He single-handedly developed Démocrite right up to the beta version. I'm taking over, and that's no mean feat.
- He was my friend, my partner at Nexai.net and Amexio.fr. I'd like to thank Amexio again for giving him all the time he needed to develop Démocrite. During all this time, Mickael was extremely happy.
