Table of Contents

Class Agent

Namespace
Prefrontal
Assembly
Prefrontal.Core.dll

An agent manages a collection of Modules that work together to achieve a goal.

Getting Started
  • To create an agent, simply instantiate it with the new operator.
  • Add modules to the agent by chaining AddModule<T>() method calls.
  • Finally call Initialize() or InitializeAsync() to initialize all modules and start the agent.
  • When the agent is no longer needed, call Dispose() to dispose of it.
The Agent Lifecycle
Agent.StateAvailable Actions
Uninitialized
  • Modules can be added.
  • Modules can be removed.
  • Modules can send and receive signals.
  • Signal processing order can be changed.
Initializing
  • Modules can be added (new modules are initialized immediately).
  • Modules cannot be removed.
  • Modules can send and receive signals.
  • Signal processing order can be changed.
Initialized
  • Modules can be added (new modules are initialized immediately).
  • Modules can be removed.
  • Modules can send and receive signals.
  • Signal processing order can be changed.
Disposing
  • Modules cannot be added.
  • Modules cannot be removed.
  • Modules can send and receive signals.
  • Signal processing order can be changed.
Disposed
  • Modules cannot be added.
  • Modules cannot be removed.
  • Modules cannot send nor receive signals.
  • Signal processing order cannot be changed.

ServiceProvider Disposal
If you need to dispose of the ServiceProvider when the agent is disposed (e.g. if the ServiceProvider was specifically created for that agent), simply add ServiceProviderDisposesWithAgentModule to the agent.
public class Agent : IDisposable
Inheritance
Agent
Implements
Inherited Members
Extension Methods

Constructors

Agent()

Creates a new agent without a ServiceProvider.

public Agent()

Agent(Action<ServiceCollection>)

Creates a new agent and uses the specified configureServices action to construct a ServiceProvider.

This constructor also adds a ServiceProviderDisposesWithAgentModule to the agent to properly dispose of the ServiceProvider when the agent gets disposed.

public Agent(Action<ServiceCollection> configureServices)

Parameters

configureServices Action<ServiceCollection>

Agent(IServiceProvider)

Creates a new agent with the specified serviceProvider.

public Agent(IServiceProvider serviceProvider)

Parameters

serviceProvider IServiceProvider

The service provider to use when solving dependencies.

Exceptions

ArgumentNullException
InvalidOperationException

Fields

Debug

The logger that is used to log debug messages.

protected ILogger Debug

Field Value

ILogger

Properties

Description

A human readable description of the agent and its purpose.

public string Description { get; set; }

Property Value

string

Initialization

Provides a task that completes when the agent is no longer Uninitialized or Initializing.
This is handy when a piece of code that didn't call InitializeAsync() needs to wait for initialization to complete.

public Task Initialization { get; }

Property Value

Task
See Also

IsRunning

public bool IsRunning { get; }

Property Value

bool

Modules

All module instances that are currently part of the agent.

public IReadOnlyList<Module> Modules { get; }

Property Value

IReadOnlyList<Module>

Name

The name of the agent.

public string Name { get; set; }

Property Value

string

ServiceProvider

The service provider that is used to inject dependencies into modules.

public IServiceProvider ServiceProvider { get; }

Property Value

IServiceProvider

State

The current state of the agent. Agents start in the Uninitialized state. Calling Initialize()/InitializeAsync() sets the state to Initializing while modules are being initialized and to Initialized when they are done. Calling Dispose() sets the state to Disposing while modules are being disposed of and to Disposed when done.

public AgentState State { get; }

Property Value

AgentState

StateObservable

An observable stream of the agent's state. Subscribing will immediately return the current state.

public IObservable<AgentState> StateObservable { get; }

Property Value

IObservable<AgentState>
See Also

Methods

AddModule(Type, Action<Module>?)

Adds a module of the specified type to the agent. The module type's constructor gets called and the required services are injected from the ServiceProvider. The module's constructor can also take the agent itself as a parameter and even other modules that it requires. The optional configure action can be used to configure the module before it is initialized. Don't forget to initialize the agent after you've added all the modules.

When a module is added after the agent is initialized it gets initialized immediately.

public Agent AddModule(Type moduleType, Action<Module>? configure = null)

Parameters

moduleType Type

The module type. A module must be a concrete class that inherits from Module.

configure Action<Module>

(optional) callback to configure the module before it is initialized.

Returns

Agent

The agent for further method chaining.

Exceptions

InvalidOperationException

Thrown when the type is not a valid module type or when the module could not be created.

AddModule<T>(Action<T>?)

Adds a module of the specified type to the agent. The module type's constructor gets called and the required services are injected from the ServiceProvider. The module's constructor can also take the agent itself as a parameter and even other modules that it requires. The optional configure action can be used to configure the module before it is initialized. Don't forget to initialize the agent after you've added all the modules.

When a module is added after the agent is initialized it gets initialized immediately.

public Agent AddModule<T>(Action<T>? configure = null) where T : Module

Parameters

configure Action<T>

(optional) callback to configure the module before it is initialized.

Returns

Agent

The agent for further method chaining.

Type Parameters

T

The module type. This must inherit from Module.

Exceptions

InvalidOperationException

Thrown when the type is not a valid module type or when the module could not be created.

AddModule<T>(Func<Agent, T>)

Adds a module using a factory function to create the module. Don't forget to initialize the agent after you've added all the modules.

When a module is added after the agent is initialized it gets initialized immediately.

public Agent AddModule<T>(Func<Agent, T> createModule) where T : Module

Parameters

createModule Func<Agent, T>

A function that creates the module.

Returns

Agent

The agent for further method chaining.

Type Parameters

T

The module type. This must inherit from Module.

Exceptions

InvalidOperationException

Thrown when the type is not a valid module type or when the module could not be created.

Dispose()

Disposes of the agent and all of its modules.
All modules that implement IDisposable will have their Dispose() method called.

public virtual void Dispose()

Remarks

If you choose to override this method in a derived class, make sure to call the base method, preferably at the start of the method.

GetModule(Type)

Gets last module that can be assigned to a variable of the specified moduleType or throws an InvalidOperationException if the agent does not contain such a module. Modules added later take precedence over modules added before them which is why the last module that meets the criteria is returned.

public Module GetModule(Type moduleType)

Parameters

moduleType Type

Returns

Module

The most significant module assignable to the given type.

GetModuleOrDefault(Type)

Gets the last module that can be assigned to a variable of the specified moduleType or null if the agent does not contain such a module. Modules added later take precedence over modules added before them which is why the last module that meets the criteria is returned.

public Module? GetModuleOrDefault(Type moduleType)

Parameters

moduleType Type

Returns

Module

The most significant module assignable to the given type or null if not found.

GetModuleOrDefault<T>()

Gets the last module that can be assigned to a variable of the specified T type or null if the agent does not contain such a module. Modules added later take precedence over modules added before them which is why the last module that meets the criteria is returned.

public T? GetModuleOrDefault<T>() where T : Module

Returns

T

The most significant module assignable to the given type or null if not found.

Type Parameters

T

GetModule<T>()

Gets the last module that can be assigned to a variable of the specified T type or throws an InvalidOperationException if the agent does not contain such a module. Modules added later take precedence over modules added before them which is why the last module that meets the criteria is returned.

public T GetModule<T>() where T : Module

Returns

T

The most significant module assignable to the given type.

Type Parameters

T

GetModulesRecursivelyThatRequire(Type)

Gets all modules on the agent that requires a module of the specified moduleType and all modules that require those modules, etc.
See RequiredModuleAttribute for more information.

public IEnumerable<Module> GetModulesRecursivelyThatRequire(Type moduleType)

Parameters

moduleType Type

Returns

IEnumerable<Module>

GetModulesRecursivelyThatRequire<T>()

Gets all modules on the agent that requires a module of type T and all modules that require those modules, etc.
See RequiredModuleAttribute for more information.

public IEnumerable<Module> GetModulesRecursivelyThatRequire<T>() where T : Module

Returns

IEnumerable<Module>

Type Parameters

T

GetModulesThatRequire(Type)

Gets all modules on the agent that requires a module of the specified moduleType.
See RequiredModuleAttribute for more information.

public IEnumerable<Module> GetModulesThatRequire(Type moduleType)

Parameters

moduleType Type

Returns

IEnumerable<Module>

GetModulesThatRequire<T>()

Gets all modules on the agent that requires a module of type T.
See RequiredModuleAttribute for more information.

public IEnumerable<Module> GetModulesThatRequire<T>() where T : Module

Returns

IEnumerable<Module>

Type Parameters

T

GetModules<T>()

Gets all modules that can be assigned to a variable of the specified T type. Modules are returned in the reverse order they were added to the agent. Modules added later take precedence over modules added before them which is why the modules are returned in reverse order.

public IEnumerable<T> GetModules<T>() where T : Module

Returns

IEnumerable<T>

All modules that can be assigned to the given type or an empty enumerable if none were found.

Type Parameters

T

GetOrAddModule(Type, Action<Module>?)

Gets the first module of the specified T type or adds a new module of that type to the agent if it does not contain one yet.

public Module GetOrAddModule(Type moduleType, Action<Module>? configure = null)

Parameters

moduleType Type
configure Action<Module>

(optional) callback to configure the module before it is initialized.

Returns

Module

The module of the specified type.

GetOrAddModule(Type, Func<Agent, Module>)

Gets the first module of the specified T type or adds a new module of that type to the agent if it does not contain one yet.

public Module GetOrAddModule(Type moduleType, Func<Agent, Module> createModule)

Parameters

moduleType Type
createModule Func<Agent, Module>

A function that creates the module.

Returns

Module

The module of the specified type.

GetOrAddModule<T>(Action<T>?)

Gets the first module of the specified T type or adds a new module of that type to the agent if it does not contain one yet.

public T GetOrAddModule<T>(Action<T>? configure = null) where T : Module

Parameters

configure Action<T>

(optional) callback to configure the module before it is initialized.

Returns

T

The module of the specified type.

Type Parameters

T

The module type.

GetOrAddModule<T>(Func<Agent, T>)

Gets the first module of the specified T type or adds a new module of that type to the agent if it does not contain one yet.

public T GetOrAddModule<T>(Func<Agent, T> createModule) where T : Module

Parameters

createModule Func<Agent, T>

A function that creates the module.

Returns

T

The module of the specified type.

Type Parameters

T

The module type.

Initialize(bool)

Initializes all modules on the agent in the order they were added.

This method calls Prefrontal.Module.Initialize() / Prefrontal.Module.InitializeAsync() on each module. If both methods are overridden in a module, the async method takes precedence.

You cannot dispose of the agent while it is initializing.

public Agent Initialize(bool synchronous = false)

Parameters

synchronous bool

Run the initialization synchronously, blocking the current thread until initialization is complete.

Returns

Agent

The fully Initialized agent if synchronous = true or if all modules initialized synchronously.

Otherwise, the agent is returned immediately while still Initializing in the background.

Exceptions

InvalidOperationException

Thrown when the agent has already been disposed of or if any of the modules attempt to dispose of the agent.

AggregateException

Thrown when one or more modules fail to initialize.

InitializeAsync()

Initializes all modules on the agent in the order they were added.

This method calls Prefrontal.Module.Initialize() / Prefrontal.Module.InitializeAsync() on each module. If both methods are overridden in a module, the async method takes precedence.

You cannot dispose of the agent while it is initializing.

public virtual Task<Agent> InitializeAsync()

Returns

Task<Agent>

A task that completes when all modules have been initialized and the agent is in the Initialized state.

Remarks

If you choose to override this method in a derived class, make sure to call and await the base method.

Exceptions

InvalidOperationException

Thrown when the agent has already been disposed of or if any of the modules attempt to dispose of the agent.

AggregateException

Thrown when one or more modules fail to initialize.

ObserveSignals<TSignal>()

Returns an IObservable<T> that emits all future signals of type TSignal that are sent on the agent.
Example:

var myAgent = new Agent()
	.AddModule<MyModule>()
	.Initialize();
myAgent
	.ObserveSignals<MySignal>()
	.Subscribe(signal => Console.WriteLine($"Received signal: {signal}"));
myAgent.SendSignal(new MySignal());

Use the ObserveSignals<TSignal>(out IObservable<TSignal>) method instead when configuring the agent in a method chain.

public IObservable<TSignal> ObserveSignals<TSignal>()

Returns

IObservable<TSignal>

The observable that emits specified signal type.

Type Parameters

TSignal

ObserveSignals<TSignal>(out IObservable<TSignal>)

Retrieves an IObservable<T> that emits all future signals of type TSignal that are sent on the agent.
Use this method when configuring the agent in a method chain. Example:

var myAgent = new Agent()
	.AddModule<MyModule>()
	.ObserveSignals<MySignal>(out var mySignals)
	.Initialize();
mySignals.Subscribe(signal => Console.WriteLine($"Received signal: {signal}"));
myAgent.SendSignal(new MySignal());

When not configuring the agent it's recommended to instead use the ObserveSignals<TSignal>() method.

public Agent ObserveSignals<TSignal>(out IObservable<TSignal> observable)

Parameters

observable IObservable<TSignal>

A reference to assign the observable to.

Returns

Agent

The agent for further configuration.

Type Parameters

TSignal

RemoveModule(params IEnumerable<Module>)

Removes the specified modules from the agent and calls Dispose() on each one that implements IDisposable.

public bool RemoveModule(params IEnumerable<Module> modules)

Parameters

modules IEnumerable<Module>

The modules to remove.

Returns

bool

True if at least one module was found and successfully removed, False otherwise.

RemoveModule(Type)

Removes all modules of the specified type from the agent and calls Dispose() on those that implements IDisposable.

public bool RemoveModule(Type moduleType)

Parameters

moduleType Type

The module type. This must inherit from Module

Returns

bool

True if at least one module of the given type was found and removed, False otherwise.

RemoveModule<T>()

Removes all modules of the specified T type from the agent and calls Dispose() on those that implements IDisposable.

public bool RemoveModule<T>() where T : Module

Returns

bool

True if at least one module of the given type was found and removed, False otherwise.

Type Parameters

T

The module type. This must inherit from Module.

RunAsync(RunningModuleExceptionPolicy, CancellationToken)

Runs the agent by calling every module's RunAsync() method in parallel.

  • Adding or removing modules while the agent is running will automatically update the list of running modules.
  • Only when all modules have finished running will the agent return control to the caller.
  • Exceptions thrown by modules are caught and handled according to the exceptionPolicy parameter.
public Task RunAsync(RunningModuleExceptionPolicy exceptionPolicy = RunningModuleExceptionPolicy.LogAndStopModule, CancellationToken cancellationToken = default)

Parameters

exceptionPolicy RunningModuleExceptionPolicy
cancellationToken CancellationToken

Returns

Task

SendSignalAsync<TSignal>(TSignal)

Sends a signal asynchronously to all modules on the agent and observers.

It's perfectly fine to add and remove modules during signal processing, but be aware that modules that are removed will not receive the signal and modules that are added will only receive future signals, i.e. sent after they were added.

public Task SendSignalAsync<TSignal>(TSignal signal)

Parameters

signal TSignal

The signal to send.

Returns

Task

A task that completes when all modules have processed the signal.

Type Parameters

TSignal

The type of signal to send.

See Also

SendSignalAsync<TSignal, TResponse>(TSignal)

Sends a signal asynchronously to all modules on the agent and observers.

It's perfectly fine to add and remove modules during signal processing, but be aware that modules that are removed will not receive the signal and modules that are added will only receive future signals, i.e. sent after they were added.

public IAsyncEnumerable<TResponse> SendSignalAsync<TSignal, TResponse>(TSignal signal)

Parameters

signal TSignal

The signal to send.

Returns

IAsyncEnumerable<TResponse>

All responses to the signal of type TResponse. XAsyncEnumerable offers a variety of methods to work with the responses.

Type Parameters

TSignal

The type of signal to send.

TResponse
See Also

SendSignal<TSignal>(TSignal)

Sends a TSignal signal to all modules and observers on the agent.

This method is non-blocking and returns immediately because it defers the signal processing to a background task.

It's perfectly fine to add and remove modules during signal processing, but be aware that modules that are removed will not receive the signal and modules that are added will only receive future signals, i.e. sent after they were added.

public void SendSignal<TSignal>(TSignal signal)

Parameters

signal TSignal

The signal to send.

Type Parameters

TSignal

The type of signal to send.

SendSignal<TSignal, TResponse>(TSignal, bool)

Sends a TSignal signal to all modules and observers on the agent.

This method is non-blocking and returns immediately because it defers the signal processing to a background task.

It's perfectly fine to add and remove modules during signal processing, but be aware that modules that are removed will not receive the signal and modules that are added will only receive future signals, i.e. sent after they were added.

public IEnumerable<TResponse> SendSignal<TSignal, TResponse>(TSignal signal, bool synchronous = false)

Parameters

signal TSignal

The signal to send.

synchronous bool

Whether to wait for all the responses before returning.

Returns

IEnumerable<TResponse>

All responses to the signal of type TResponse. Enumerable offers a variety of methods to work with the responses.

Type Parameters

TSignal

The type of signal to send.

TResponse

The type of response to expect.

SetSignalProcessingOrder<TSignal>(Func<Agent, Module[]>)

Specifies the order in which modules must process signals of the given type.
Example:

var myAgent = new Agent()
	.AddModule<LastModule>()
	.AddModule<ThirdModule>()
	.AddModule<SecondModule>()
	.AddModule<FirstModule>()
	.SetSignalProcessingOrder<MySignal>(a =>
	[
		a.GetModule<FirstModule>(),
		a.GetModule<SecondModule>(),
		a.GetModule<ThirdModule>(),
		a.GetModule<LastModule>(),
	])
	.Initialize();
public Agent SetSignalProcessingOrder<TSignal>(Func<Agent, Module[]> getModuleOrder)

Parameters

getModuleOrder Func<Agent, Module[]>

A function that returns the order in which modules should process the signals.

Returns

Agent

Type Parameters

TSignal

Stop()

public void Stop()

ToString()

Returns a single-line string representation of the agent and its modules.
If you need a more verbose multiline string representation, use ToStringPretty() instead.

Unlike ToStringPretty(), this method lists each module's type instead of calling their ToString() method.

Example:
Console.WriteLine(
	new Agent
	{
		Name = "foobar",
		Description = "A placeholder agent",
	}
	.AddModule<FooModule>()
	.AddModule<BarModule>()
	.Initialize()
	.ToString()
);
public class FooModule : Module { }
public class BarModule : Module { }
// Output:
// Agent foobar { Foo, Bar }
public override string ToString()

Returns

string

A string in the format of "Agent Name { Modules }"

ToStringPretty(int)

Returns a multiline string representation of the agent and its modules.

Unlike ToString() this method includes the Description and lists the results of calling each module's ToString().

Examples:
Agent Description fits on one line (does not exceed maxWidth).
Console.WriteLine(
	new Agent
	{
		Name = "foobar",
		Description = "A placeholder agent",
	}
	.AddModule<FooModule>()
	.AddModule<BarModule>()
	.Initialize()
	.ToStringPretty()
);
public class FooModule : Module { }
public class BarModule : Module { }
// Output:
// Agent foobar
// ╭─────────────────────╮
// │ A placeholder agent │
// ╰─────────────────────╯
//   ├─ Foo
//   └─ Bar
Agent Description and Foo.ToString() gets wrapped (length exceeds maxWidth).
Console.WriteLine(
	new Agent
	{
		Name = "foobar",
		Description = "A placeholder agent with a seriously long description that needs to be wrapped around",
	}
	.AddModule<FooModule>()
	.AddModule<BarModule>()
	.Initialize()
	.ToStringPretty()
);
public class FooModule : Module
{
	public override string ToString() => "Foo with a long description that needs to be wrapped around";
}
public class BarModule : Module { }
// Output:
// Agent foobar
// ╭─────────────────────────────────────────────╮
// │ A placeholder agent with a seriously long   │
// │ description that needs to be wrapped around │
// ╰─────────────────────────────────────────────╯
//   ├─ Foo with a long description that needs to be
//   │  wrapped around
//   └─ Bar
public virtual string ToStringPretty(int maxWidth = 50)

Parameters

maxWidth int

The maximum width of each line in the output.

Returns

string

A string representation of the agent which includes the Name, Description and Modules.