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
configureServices
Action<ServiceCollection>
Agent(IServiceProvider)
Creates a new agent with the specified serviceProvider
.
public Agent(IServiceProvider serviceProvider)
Parameters
serviceProvider
IServiceProviderThe service provider to use when solving dependencies.
Exceptions
Fields
Debug
The logger that is used to log debug messages.
protected ILogger Debug
Field 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
moduleType
TypeThe 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
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
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
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
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
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
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
Typeconfigure
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
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
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
boolRun 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
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
RunningModuleExceptionPolicycancellationToken
CancellationToken
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
signal
TSignalThe 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
TSignalThe 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
TSignalThe 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
TSignalThe signal to send.
synchronous
boolWhether 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
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
// └─ 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
intThe maximum width of each line in the output.
Returns
- string
A string representation of the agent which includes the Name, Description and Modules.