Repository: microsoft/autogen
Pull request: Agentbase refactor
Remove some unused code and refactor so that IAgentBase/IAgentContext open up possibility of abstracting the grpc services so that we can also provide a completely in-memory runtime.

closes #3950 

Problem statement:
decouple .NET grpc and runtime so that runtime is pluggable
### What feature would you like to be added?

we really could do with having the grace layer disentangled from Orleans/in-memory/dapr etc so the the runtime and api protocol are separated. 

### Why is this needed?

separation of concerns

Hints from issue discussion:

Commit message: Merge branch 'main' into agentbase-refactor

Patch to review:
diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln
index 83147d38dc7b..5d83a07f0a9c 100644
--- a/dotnet/AutoGen.sln
+++ b/dotnet/AutoGen.sln
@@ -127,6 +127,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{686480
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloAgentState", "samples\Hello\HelloAgentState\HelloAgentState.csproj", "{64EF61E7-00A6-4E5E-9808-62E10993A0E5}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloAgentState", "samples\Hello\HelloAgentState\HelloAgentState.csproj", "{64EF61E7-00A6-4E5E-9808-62E10993A0E5}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
diff --git a/dotnet/samples/Hello/HelloAgent/README.md b/dotnet/samples/Hello/HelloAgent/README.md
index 795eed07281d..2de64ebaa01b 100644
--- a/dotnet/samples/Hello/HelloAgent/README.md
+++ b/dotnet/samples/Hello/HelloAgent/README.md
@@ -118,4 +118,4 @@ message ReadmeRequested {
   </ItemGroup>
 ```
 
-You can send messages using the [```Microsoft.AutoGen.Agents.AgentClient``` class](autogen/dotnet/src/Microsoft.AutoGen/Agents/AgentClient.cs). Messages are wrapped in [the CloudEvents specification](https://cloudevents.io) and sent to the event bus.
+You can send messages using the [```Microsoft.AutoGen.Agents.AgentWorker``` class](autogen/dotnet/src/Microsoft.AutoGen/Agents/AgentWorker.cs). Messages are wrapped in [the CloudEvents specification](https://cloudevents.io) and sent to the event bus.
diff --git a/dotnet/samples/Hello/HelloAgentState/README.md b/dotnet/samples/Hello/HelloAgentState/README.md
index 06c4883182c9..0079005d2450 100644
--- a/dotnet/samples/Hello/HelloAgentState/README.md
+++ b/dotnet/samples/Hello/HelloAgentState/README.md
@@ -117,7 +117,7 @@ message ReadmeRequested {
   </ItemGroup>
 ```
 
-You can send messages using the [```Microsoft.AutoGen.Agents``` class](autogen/dotnet/src/Microsoft.AutoGen/Agents/AgentClient.cs). Messages are wrapped in [the CloudEvents specification](https://cloudevents.io) and sent to the event bus.
+You can send messages using the [```Microsoft.AutoGen.Agents.AgentWorker``` class](autogen/dotnet/src/Microsoft.AutoGen/Agents/AgentWorker.cs). Messages are wrapped in [the CloudEvents specification](https://cloudevents.io) and sent to the event bus.
 
 ### Managing State
 
diff --git a/dotnet/samples/dev-team/DevTeam.Backend/Program.cs b/dotnet/samples/dev-team/DevTeam.Backend/Program.cs
index d860f4050b47..9de188bdfe8c 100644
--- a/dotnet/samples/dev-team/DevTeam.Backend/Program.cs
+++ b/dotnet/samples/dev-team/DevTeam.Backend/Program.cs
@@ -23,7 +23,7 @@
     //.AddAgent<Sandbox>(nameof(Sandbox))
     .AddAgent<Hubber>(nameof(Hubber));
 
-builder.Services.AddSingleton<AgentClient>();
+builder.Services.AddSingleton<AgentWorker>();
 builder.Services.AddSingleton<WebhookEventProcessor, GithubWebHookProcessor>();
 builder.Services.AddSingleton<GithubAuthService>();
 builder.Services.AddSingleton<IManageAzure, AzureService>();
diff --git a/dotnet/samples/dev-team/DevTeam.Backend/Services/GithubWebHookProcessor.cs b/dotnet/samples/dev-team/DevTeam.Backend/Services/GithubWebHookProcessor.cs
index 85b407c4e333..e72b511e2381 100644
--- a/dotnet/samples/dev-team/DevTeam.Backend/Services/GithubWebHookProcessor.cs
+++ b/dotnet/samples/dev-team/DevTeam.Backend/Services/GithubWebHookProcessor.cs
@@ -10,10 +10,10 @@
 
 namespace DevTeam.Backend;
 
-public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logger, AgentClient client) : WebhookEventProcessor
+public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logger, AgentWorker client) : WebhookEventProcessor
 {
     private readonly ILogger<GithubWebHookProcessor> _logger = logger;
-    private readonly AgentClient _client = client;
+    private readonly AgentWorker _client = client;
 
     protected override async Task ProcessIssuesWebhookAsync(WebhookHeaders headers, IssuesEvent issuesEvent, IssuesAction action)
     {
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs b/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs
index 62779f8366c7..d4a6ca0f4ee1 100644
--- a/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs
+++ b/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs
@@ -8,18 +8,17 @@
 
 namespace Microsoft.AutoGen.Agents;
 
-public abstract class AgentBase
+public abstract class AgentBase : IAgentBase
 {
     public static readonly ActivitySource s_source = new("AutoGen.Agent");
+    public AgentId AgentId => _context.AgentId;
     private readonly object _lock = new();
     private readonly Dictionary<string, TaskCompletionSource<RpcResponse>> _pendingRequests = [];
 
     private readonly Channel<object> _mailbox = Channel.CreateUnbounded<object>();
     private readonly IAgentContext _context;
-
-    protected internal AgentId AgentId => _context.AgentId;
     protected internal ILogger Logger => _context.Logger;
-    protected internal IAgentContext Context => _context;
+    public IAgentContext Context => _context;
     protected readonly EventTypes EventTypes;
 
     protected AgentBase(IAgentContext context, EventTypes eventTypes)
@@ -54,7 +53,7 @@ internal Task Start()
         }
     }
 
-    internal void ReceiveMessage(Message message) => _mailbox.Writer.TryWrite(message);
+    public void ReceiveMessage(Message message) => _mailbox.Writer.TryWrite(message);
 
     private async Task RunMessagePump()
     {
@@ -79,7 +78,7 @@ private async Task RunMessagePump()
         }
     }
 
-    private async Task HandleRpcMessage(Message msg)
+    protected internal async Task HandleRpcMessage(Message msg)
     {
         switch (msg.MessageCase)
         {
@@ -108,12 +107,12 @@ await this.InvokeWithActivityAsync(
                 break;
         }
     }
-    protected async Task Store(AgentState state)
+    public async Task Store(AgentState state)
     {
         await _context.Store(state).ConfigureAwait(false);
         return;
     }
-    protected async Task<T> Read<T>(AgentId agentId) where T : IMessage, new()
+    public async Task<T> Read<T>(AgentId agentId) where T : IMessage, new()
     {
         var agentstate = await _context.Read(agentId).ConfigureAwait(false);
         return agentstate.FromAgentState<T>();
@@ -132,7 +131,6 @@ private void OnResponseCore(RpcResponse response)
 
         completion.SetResult(response);
     }
-
     private async Task OnRequestCore(RpcRequest request)
     {
         RpcResponse response;
@@ -193,7 +191,7 @@ static async ((AgentBase Agent, RpcRequest Request, TaskCompletionSource<RpcResp
         return await completion.Task.ConfigureAwait(false);
     }
 
-    protected async ValueTask PublishEvent(CloudEvent item)
+    public async ValueTask PublishEvent(CloudEvent item)
     {
         var activity = s_source.StartActivity($"PublishEvent '{item.Type}'", ActivityKind.Client, Activity.Current?.Context ?? default);
         activity?.SetTag("peer.service", $"{item.Type}/{item.Source}");
@@ -225,5 +223,5 @@ public Task CallHandler(CloudEvent item)
         return Task.CompletedTask;
     }
 
-    protected virtual Task<RpcResponse> HandleRequest(RpcRequest request) => Task.FromResult(new RpcResponse { Error = "Not implemented" });
+    public virtual Task<RpcResponse> HandleRequest(RpcRequest request) => Task.FromResult(new RpcResponse { Error = "Not implemented" });
 }
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AgentClient.cs b/dotnet/src/Microsoft.AutoGen/Agents/AgentClient.cs
deleted file mode 100644
index 369d717af3b7..000000000000
--- a/dotnet/src/Microsoft.AutoGen/Agents/AgentClient.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Diagnostics;
-using Google.Protobuf;
-using Microsoft.AutoGen.Abstractions;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AutoGen.Agents;
-public sealed class AgentClient(ILogger<AgentClient> logger, AgentWorkerRuntime runtime, DistributedContextPropagator distributedContextPropagator,
-    [FromKeyedServices("EventTypes")] EventTypes eventTypes)
-    : AgentBase(new ClientContext(logger, runtime, distributedContextPropagator), eventTypes)
-{
-    public async ValueTask PublishEventAsync(CloudEvent evt) => await PublishEvent(evt);
-    public async ValueTask<RpcResponse> SendRequestAsync(AgentId target, string method, Dictionary<string, string> parameters) => await RequestAsync(target, method, parameters);
-    public async ValueTask PublishEventAsync(string topic, IMessage evt)
-    {
-        await PublishEventAsync(evt.ToCloudEvent(topic)).ConfigureAwait(false);
-    }
-    private sealed class ClientContext(ILogger<AgentClient> logger, AgentWorkerRuntime runtime, DistributedContextPropagator distributedContextPropagator) : IAgentContext
-    {
-        public AgentId AgentId { get; } = new AgentId("client", Guid.NewGuid().ToString());
-        public AgentBase? AgentInstance { get; set; }
-        public ILogger Logger { get; } = logger;
-        public DistributedContextPropagator DistributedContextPropagator { get; } = distributedContextPropagator;
-        public async ValueTask PublishEventAsync(CloudEvent @event)
-        {
-            await runtime.PublishEvent(@event).ConfigureAwait(false);
-        }
-        public async ValueTask SendRequestAsync(AgentBase agent, RpcRequest request)
-        {
-            await runtime.SendRequest(AgentInstance!, request).ConfigureAwait(false);
-        }
-
-        public async ValueTask SendResponseAsync(RpcRequest request, RpcResponse response)
-        {
-            await runtime.SendResponse(response).ConfigureAwait(false);
-        }
-        public ValueTask Store(AgentState value)
-        {
-            throw new NotImplementedException();
-        }
-        public ValueTask<AgentState> Read(AgentId agentId)
-        {
-            throw new NotImplementedException();
-        }
-    }
-}
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AgentContext.cs b/dotnet/src/Microsoft.AutoGen/Agents/AgentContext.cs
index 779cc86a608a..31bc13b06488 100644
--- a/dotnet/src/Microsoft.AutoGen/Agents/AgentContext.cs
+++ b/dotnet/src/Microsoft.AutoGen/Agents/AgentContext.cs
@@ -4,9 +4,9 @@
 
 namespace Microsoft.AutoGen.Agents;
 
-internal sealed class AgentContext(AgentId agentId, AgentWorkerRuntime runtime, ILogger<AgentBase> logger, DistributedContextPropagator distributedContextPropagator) : IAgentContext
+internal sealed class AgentContext(AgentId agentId, IAgentWorkerRuntime runtime, ILogger<AgentBase> logger, DistributedContextPropagator distributedContextPropagator) : IAgentContext
 {
-    private readonly AgentWorkerRuntime _runtime = runtime;
+    private readonly IAgentWorkerRuntime _runtime = runtime;
 
     public AgentId AgentId { get; } = agentId;
     public ILogger Logger { get; } = logger;
@@ -19,18 +19,18 @@ public async ValueTask SendResponseAsync(RpcRequest request, RpcResponse respons
     }
     public async ValueTask SendRequestAsync(AgentBase agent, RpcRequest request)
     {
-        await _runtime.SendRequest(agent, request);
+        await _runtime.SendRequest(agent, request).ConfigureAwait(false);
     }
     public async ValueTask PublishEventAsync(CloudEvent @event)
     {
-        await _runtime.PublishEvent(@event);
+        await _runtime.PublishEvent(@event).ConfigureAwait(false);
     }
     public async ValueTask Store(AgentState value)
     {
-        await _runtime.Store(value);
+        await _runtime.Store(value).ConfigureAwait(false);
     }
     public async ValueTask<AgentState> Read(AgentId agentId)
     {
-        return await _runtime.Read(agentId);
+        return await _runtime.Read(agentId).ConfigureAwait(false);
     }
 }
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AgentWorker.cs b/dotnet/src/Microsoft.AutoGen/Agents/AgentWorker.cs
new file mode 100644
index 000000000000..0ba909ac61d5
--- /dev/null
+++ b/dotnet/src/Microsoft.AutoGen/Agents/AgentWorker.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics;
+using Google.Protobuf;
+using Microsoft.AutoGen.Abstractions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AutoGen.Agents;
+public sealed class AgentWorker(IAgentWorkerRuntime runtime, DistributedContextPropagator distributedContextPropagator,
+    [FromKeyedServices("EventTypes")] EventTypes eventTypes, ILogger<AgentBase> logger)
+    : AgentBase(new AgentContext(new AgentId("client", Guid.NewGuid().ToString()), runtime, logger, distributedContextPropagator), eventTypes)
+{
+    public async ValueTask PublishEventAsync(CloudEvent evt) => await PublishEvent(evt);
+
+    public async ValueTask PublishEventAsync(string topic, IMessage evt)
+    {
+        await PublishEventAsync(evt.ToCloudEvent(topic)).ConfigureAwait(false);
+    }
+}
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/App.cs b/dotnet/src/Microsoft.AutoGen/Agents/App.cs
index 00c487ede6fb..be5da1ac5772 100644
--- a/dotnet/src/Microsoft.AutoGen/Agents/App.cs
+++ b/dotnet/src/Microsoft.AutoGen/Agents/App.cs
@@ -44,7 +44,7 @@ public static async ValueTask<WebApplication> PublishMessageAsync(
         {
             await StartAsync(builder, agents, local);
         }
-        var client = Host.Services.GetRequiredService<AgentClient>() ?? throw new InvalidOperationException("Host not started");
+        var client = Host.Services.GetRequiredService<AgentWorker>() ?? throw new InvalidOperationException("Host not started");
         await client.PublishEventAsync(topic, message).ConfigureAwait(false);
         return Host;
     }
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AgentWorkerRuntime.cs b/dotnet/src/Microsoft.AutoGen/Agents/GrpcAgentWorkerRuntime.cs
similarity index 97%
rename from dotnet/src/Microsoft.AutoGen/Agents/AgentWorkerRuntime.cs
rename to dotnet/src/Microsoft.AutoGen/Agents/GrpcAgentWorkerRuntime.cs
index f335881fc09b..56feca8cc4c3 100644
--- a/dotnet/src/Microsoft.AutoGen/Agents/AgentWorkerRuntime.cs
+++ b/dotnet/src/Microsoft.AutoGen/Agents/GrpcAgentWorkerRuntime.cs
@@ -10,7 +10,7 @@
 
 namespace Microsoft.AutoGen.Agents;
 
-public sealed class AgentWorkerRuntime : IHostedService, IDisposable, IAgentWorkerRuntime
+public sealed class GrpcAgentWorkerRuntime : IHostedService, IDisposable, IAgentWorkerRuntime
 {
     private readonly object _channelLock = new();
     private readonly ConcurrentDictionary<string, Type> _agentTypes = new();
@@ -26,19 +26,19 @@ public sealed class AgentWorkerRuntime : IHostedService, IDisposable, IAgentWork
     private readonly AgentRpc.AgentRpcClient _client;
     private readonly IServiceProvider _serviceProvider;
     private readonly IEnumerable<Tuple<string, Type>> _configuredAgentTypes;
-    private readonly ILogger<AgentWorkerRuntime> _logger;
+    private readonly ILogger<GrpcAgentWorkerRuntime> _logger;
     private readonly DistributedContextPropagator _distributedContextPropagator;
     private readonly CancellationTokenSource _shutdownCts;
     private AsyncDuplexStreamingCall<Message, Message>? _channel;
     private Task? _readTask;
     private Task? _writeTask;
 
-    public AgentWorkerRuntime(
+    public GrpcAgentWorkerRuntime(
         AgentRpc.AgentRpcClient client,
         IHostApplicationLifetime hostApplicationLifetime,
         IServiceProvider serviceProvider,
         [FromKeyedServices("AgentTypes")] IEnumerable<Tuple<string, Type>> configuredAgentTypes,
-        ILogger<AgentWorkerRuntime> logger,
+        ILogger<GrpcAgentWorkerRuntime> logger,
         DistributedContextPropagator distributedContextPropagator)
     {
         _client = client;
@@ -322,10 +322,6 @@ public async Task StopAsync(CancellationToken cancellationToken)
             _channel?.Dispose();
         }
     }
-    public ValueTask SendRequest(RpcRequest request)
-    {
-        throw new NotImplementedException();
-    }
     public ValueTask Store(AgentState value)
     {
         var agentId = value.AgentId ?? throw new InvalidOperationException("AgentId is required when saving AgentState.");
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/HostBuilderExtensions.cs b/dotnet/src/Microsoft.AutoGen/Agents/HostBuilderExtensions.cs
index 0e29195bff6b..f13756f6ddad 100644
--- a/dotnet/src/Microsoft.AutoGen/Agents/HostBuilderExtensions.cs
+++ b/dotnet/src/Microsoft.AutoGen/Agents/HostBuilderExtensions.cs
@@ -49,9 +49,9 @@ public static AgentApplicationBuilder AddAgentWorker(this IHostApplicationBuilde
             });
         });
         builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
-        builder.Services.AddSingleton<AgentClient>();
-        builder.Services.AddSingleton<AgentWorkerRuntime>();
-        builder.Services.AddSingleton<IHostedService>(sp => sp.GetRequiredService<AgentWorkerRuntime>());
+        builder.Services.AddSingleton<IAgentWorkerRuntime, GrpcAgentWorkerRuntime>();
+        builder.Services.AddSingleton<IHostedService>(sp => (IHostedService)sp.GetRequiredService<IAgentWorkerRuntime>());
+        builder.Services.AddSingleton<AgentWorker>();
         builder.Services.AddKeyedSingleton("EventTypes", (sp, key) =>
         {
             var interfaceType = typeof(IMessage);
@@ -111,7 +111,7 @@ public sealed class AgentTypes(Dictionary<string, Type> types)
                                 .SelectMany(assembly => assembly.GetTypes())
                                 .Where(type => ReflectionHelper.IsSubclassOfGeneric(type, typeof(AgentBase))
                                     && !type.IsAbstract
-                                    && !type.Name.Equals("AgentClient"))
+                                    && !type.Name.Equals("AgentWorker"))
                                 .ToDictionary(type => type.Name, type => type);
 
         return new AgentTypes(agents);
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IAgentBase.cs b/dotnet/src/Microsoft.AutoGen/Agents/IAgentBase.cs
index 122dff2c6270..1e5a809ea4d6 100644
--- a/dotnet/src/Microsoft.AutoGen/Agents/IAgentBase.cs
+++ b/dotnet/src/Microsoft.AutoGen/Agents/IAgentBase.cs
@@ -1,22 +1,20 @@
+using Google.Protobuf;
 using Microsoft.AutoGen.Abstractions;
-using Microsoft.Extensions.Logging;
 
 namespace Microsoft.AutoGen.Agents
 {
     public interface IAgentBase
     {
         // Properties
-        string AgentId { get; }
-        ILogger Logger { get; }
+        AgentId AgentId { get; }
         IAgentContext Context { get; }
 
         // Methods
         Task CallHandler(CloudEvent item);
         Task<RpcResponse> HandleRequest(RpcRequest request);
-        Task Start();
-        Task ReceiveMessage(Message message);
+        void ReceiveMessage(Message message);
         Task Store(AgentState state);
-        Task<T> Read<T>(AgentId agentId);
-        Task PublishEvent(CloudEvent item);
+        Task<T> Read<T>(AgentId agentId) where T : IMessage, new();
+        ValueTask PublishEvent(CloudEvent item);
     }
 }
diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IAgentWorkerRuntime.cs b/dotnet/src/Microsoft.AutoGen/Agents/IAgentWorkerRuntime.cs
index ee1f6e4e2a13..b40f8fe4be51 100644
--- a/dotnet/src/Microsoft.AutoGen/Agents/IAgentWorkerRuntime.cs
+++ b/dotnet/src/Microsoft.AutoGen/Agents/IAgentWorkerRuntime.cs
@@ -6,6 +6,8 @@ namespace Microsoft.AutoGen.Agents;
 public interface IAgentWorkerRuntime
 {
     ValueTask PublishEvent(CloudEvent evt);
-    ValueTask SendRequest(RpcRequest request);
+    ValueTask SendRequest(AgentBase agent, RpcRequest request);
     ValueTask SendResponse(RpcResponse response);
+    ValueTask Store(AgentState value);
+    ValueTask<AgentState> Read(AgentId agentId);
 }


Code review comments:
diff_hunk: @@ -19,18 +19,18 @@ public async ValueTask SendResponseAsync(RpcRequest request, RpcResponse respons
     }
     public async ValueTask SendRequestAsync(AgentBase agent, RpcRequest request)
     {
-        await _runtime.SendRequest(agent, request);
+        await _runtime.SendRequest(agent, request).ConfigureAwait(false);
     }
     public async ValueTask PublishEventAsync(CloudEvent @event)
     {
-        await _runtime.PublishEvent(@event);
+        await _runtime.PublishEvent(@event).ConfigureAwait(false);
     }
     public async ValueTask Store(AgentState value)
     {
-        await _runtime.Store(value);
+        await _runtime.Store(value).ConfigureAwait(false);
     }
     public async ValueTask<AgentState> Read(AgentId agentId)
     {
-        return await _runtime.Read(agentId);
+        return await _runtime.Read(agentId).ConfigureAwait(false);
path: dotnet/src/Microsoft.AutoGen/Agents/AgentContext.cs
text: @user1:
Since the call is just being passed along, consider removing `async` from the signature and passing the underlying ValueTask along directly.

diff_hunk: @@ -6,6 +6,8 @@ namespace Microsoft.AutoGen.Agents;
 public interface IAgentWorkerRuntime
 {
     ValueTask PublishEvent(CloudEvent evt);
-    ValueTask SendRequest(RpcRequest request);
+    ValueTask SendRequest(AgentBase agent, RpcRequest request);
path: dotnet/src/Microsoft.AutoGen/Agents/IAgentWorkerRuntime.cs
text: @user1:
Should be `IAgentBase`?

@author:
yes