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.State | Available Actions | 
|---|---|
| Uninitialized | 
 | 
| Initializing | 
 | 
| Initialized | 
 | 
| Disposing | 
 | 
| Disposed | 
 | 
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
- configureServicesAction<ServiceCollection>
Agent(IServiceProvider)
Creates a new agent with the specified serviceProvider.
public Agent(IServiceProvider serviceProvider)Parameters
- serviceProviderIServiceProvider
- The service provider to use when solving dependencies. 
Exceptions
Fields
Debug
The logger that is used to log debug messages.
protected ILogger DebugField Value
Properties
Description
A human readable description of the agent and its purpose.
public string Description { get; set; }Property Value
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
- See Also
IsRunning
public bool IsRunning { get; }Property Value
Modules
All module instances that are currently part of the agent.
public IReadOnlyList<Module> Modules { get; }Property Value
Name
The name of the agent.
public string Name { get; set; }Property Value
ServiceProvider
The service provider that is used to inject dependencies into modules.
public IServiceProvider ServiceProvider { get; }Property Value
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
StateObservable
An observable stream of the agent's state. Subscribing will immediately return the current state.
public IObservable<AgentState> StateObservable { get; }Property Value
- 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
- moduleTypeType
- The module type. A module must be a concrete class that inherits from Module. 
- configureAction<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 : ModuleParameters
- configureAction<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 : ModuleParameters
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
- moduleTypeType
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
- moduleTypeType
Returns
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 : ModuleReturns
- 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 : ModuleReturns
- 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
- moduleTypeType
Returns
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 : ModuleReturns
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
- moduleTypeType
Returns
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 : ModuleReturns
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 : ModuleReturns
- 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
- moduleTypeType
- configureAction<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
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 : ModuleParameters
- configureAction<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 : ModuleParameters
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
- synchronousbool
- 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
- observableIObservable<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
- modulesIEnumerable<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
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 : ModuleReturns
- 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 exceptionPolicyparameter.
public Task RunAsync(RunningModuleExceptionPolicy exceptionPolicy = RunningModuleExceptionPolicy.LogAndStopModule, CancellationToken cancellationToken = default)Parameters
- exceptionPolicyRunningModuleExceptionPolicy
- cancellationTokenCancellationToken
Returns
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
- signalTSignal
- 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
- signalTSignal
- 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
- signalTSignal
- 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
- signalTSignal
- The signal to send. 
- synchronousbool
- 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
- getModuleOrderFunc<Agent, Module[]>
- A function that returns the order in which modules should process the signals. 
Returns
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
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
//   └─ BarFoo.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
//   └─ Barpublic virtual string ToStringPretty(int maxWidth = 50)Parameters
- maxWidthint
- The maximum width of each line in the output. 
Returns
- string
- A string representation of the agent which includes the Name, Description and Modules.