~#;lt=s^6_OccmRd>o{*=>)KS=lM
zZ!)iG|8G0-9s3VLm`bsa6e
ze*TlRxAjXtm^F8V`M1%s5d@tYS>&+_ga#xKGb|!oUBx3uc@mj1%=MaH4GR0tPBG_&
z9OZE;->dO@`Q)nr<%dHAsEZRKl
zedN6+3+uGHejJp;Q==pskSAcRcyh@6mjm2z-uG;s%dM-u0*u##7OxI7wwyCGpS?4U
zBFAr(%GBv5j$jS@@t@iI8?ZqE36I^4t+P^J9D^ELbS5KMtZ
z{Qn#JnSd$15nJ$ggkF%I4yUQC+BjDF^}AtB7w348EL>7#sAsLWs}ndp8^DsAcOIL9
zTOO!!0!k2`9BLk25)NeZp7ev>I1Mn={cWI3Yhx2Q#DnAo4IphoV~R^c0x&nw*MoIV
zPthX?{6{u}sMS(MxD*dmd5rU(YazQE59b|TsB5Tm)I4a!VaN@HYOR)DwH1U5y(E)z
zQqQU*B%MwtRQ$%x&;1p%ANmc|PkoFJZ%<-uq%PX&C!c-7ypis=eP+FCeuv+B@h#{4
zGx1m0PjS~FJt}3mdt4c!lel`1;4W|03kcZRG+DzkTy|7-F~eDsV2Tx!73dM0H0CTh
zl)F-YUkE1zEzEW(;JXc|KR5{ox%YTh{$%F$a36JP6Nb<0%#NbSh$dMYF-{
z1_x(Vx)}fs?5_|!5xBTWiiIQHG<%)*e=45Fhjw_tlnmlixq;mUdC$R8v#j(
zhQ$9YR-o%i5Uc`S?6EC51!bTRK=Xkyb<18FkCKnS2;o*qlij1YA@-nRpq#OMTX&RbL<^2q@0qja!uIvI;j$6>~k@IMwD42=8$$!+R^@5o6HX(*n~
+[JsonConverter(typeof(Converter))]
public readonly struct ActorId : IEquatable
{
///
@@ -113,4 +116,28 @@ public readonly struct ActorId : IEquatable
return true;
#endif
}
+
+ ///
+ /// JSON converter for .
+ ///
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override ActorId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.String)
+ {
+ throw new JsonException("Expected string value for ActorId");
+ }
+
+ string? actorIdString = reader.GetString() ?? throw new JsonException("ActorId cannot be null");
+ return ActorId.Parse(actorIdString);
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, ActorId value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString());
+ }
+ }
}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorJsonContext.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorJsonContext.cs
new file mode 100644
index 0000000000..df87360eb4
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorJsonContext.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Source-generated JSON type information for use by all Actor abstractions.
+///
+[JsonSourceGenerationOptions(
+ JsonSerializerDefaults.Web,
+ UseStringEnumConverter = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ WriteIndented = false)]
+[JsonSerializable(typeof(ActorMessage))]
+[JsonSerializable(typeof(ActorRequestMessage))]
+[JsonSerializable(typeof(ActorResponseMessage))]
+[JsonSerializable(typeof(ActorWriteOperation))]
+[JsonSerializable(typeof(SetValueOperation))]
+[JsonSerializable(typeof(RemoveKeyOperation))]
+[JsonSerializable(typeof(SendRequestOperation))]
+[JsonSerializable(typeof(UpdateRequestOperation))]
+[JsonSerializable(typeof(ActorReadOperation))]
+[JsonSerializable(typeof(ListKeysOperation))]
+[JsonSerializable(typeof(GetValueOperation))]
+[JsonSerializable(typeof(ActorReadResult))]
+[JsonSerializable(typeof(ListKeysResult))]
+[JsonSerializable(typeof(GetValueResult))]
+[JsonSerializable(typeof(ActorRequest))]
+[JsonSerializable(typeof(ActorRequestUpdate))]
+[JsonSerializable(typeof(ActorResponse))]
+[JsonSerializable(typeof(ActorId))]
+[JsonSerializable(typeof(RequestStatus))]
+[JsonSerializable(typeof(ActorWriteOperationBatch))]
+[JsonSerializable(typeof(ActorReadOperationBatch))]
+[JsonSerializable(typeof(ReadResponse))]
+[JsonSerializable(typeof(WriteResponse))]
+[JsonSerializable(typeof(ActorType))]
+[JsonSerializable(typeof(JsonElement))]
+internal sealed partial class ActorJsonContext : JsonSerializerContext;
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessage.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessage.cs
new file mode 100644
index 0000000000..c81a9ff244
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessage.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for all actor messages that can be sent between actors.
+///
+///
+/// This abstract class serves as the foundation for all actor message types.
+/// Each concrete implementation represents a specific type of message,
+/// such as request messages or response messages.
+///
+//[JsonConverter(typeof(Converter))]
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
+[JsonDerivedType(typeof(ActorRequestMessage), "request")]
+[JsonDerivedType(typeof(ActorResponseMessage), "response")]
+public abstract class ActorMessage
+{
+ /// Prevent external derivations.
+ private protected ActorMessage()
+ {
+ }
+
+ ///
+ /// Gets the type of the message.
+ ///
+ [JsonIgnore]
+ public abstract ActorMessageType Type { get; }
+
+ ///
+ /// Additional properties that can be used to extend the message with custom data.
+ ///
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessageType.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessageType.cs
new file mode 100644
index 0000000000..46b3c689a5
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessageType.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Specifies the type of actor message.
+///
+public enum ActorMessageType
+{
+ ///
+ /// Represents a request message sent to an actor.
+ ///
+ [JsonStringEnumMemberName("request")]
+ Request,
+
+ ///
+ /// Represents a response message sent from an actor.
+ ///
+ [JsonStringEnumMemberName("response")]
+ Response
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessageWriteOperation.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessageWriteOperation.cs
new file mode 100644
index 0000000000..5e365927eb
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorMessageWriteOperation.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for write operations that modify an actor's messaging (inbox/outbox).
+///
+public abstract class ActorMessageWriteOperation : ActorWriteOperation
+{
+ /// Prevent external derivations.
+ private protected ActorMessageWriteOperation()
+ {
+ }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperation.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperation.cs
new file mode 100644
index 0000000000..1375fe639c
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperation.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for all actor read operations that can query actor state or messaging.
+///
+///
+/// This abstract class serves as the foundation for all actor read operation types.
+/// Each concrete implementation represents a specific type of read operation,
+/// such as querying actor state or messaging information.
+///
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
+[JsonDerivedType(typeof(ListKeysOperation), "list_keys")]
+[JsonDerivedType(typeof(GetValueOperation), "get_value")]
+public abstract class ActorReadOperation
+{
+ /// Prevent external derivations.
+ private protected ActorReadOperation()
+ {
+ }
+
+ ///
+ /// Gets the type of the read operation.
+ ///
+ [JsonIgnore]
+ public abstract ActorReadOperationType Type { get; }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperationBatch.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperationBatch.cs
new file mode 100644
index 0000000000..6a44e35095
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperationBatch.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents a batch of read operations to be performed on an actor.
+///
+/// The collection of read operations to perform.
+public class ActorReadOperationBatch(IReadOnlyList operations)
+{
+ ///
+ /// Gets the collection of read operations to perform.
+ ///
+ [JsonPropertyName("operations")]
+ public IReadOnlyList Operations { get; } = operations;
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperationType.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperationType.cs
new file mode 100644
index 0000000000..17e2a45ebe
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadOperationType.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Specifies the type of actor read operation.
+///
+public enum ActorReadOperationType
+{
+ ///
+ /// Represents a list keys operation.
+ ///
+ [JsonStringEnumMemberName("list_keys")]
+ ListKeys,
+
+ ///
+ /// Represents a get value operation.
+ ///
+ [JsonStringEnumMemberName("get_value")]
+ GetValue
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadResult.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadResult.cs
new file mode 100644
index 0000000000..e83ce179f4
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadResult.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for all actor read operation results.
+///
+///
+/// This abstract class serves as the foundation for all actor read operation result types.
+/// Each concrete implementation represents a specific type of read operation result,
+/// such as listing keys or retrieving values from an actor's state.
+///
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
+[JsonDerivedType(typeof(ListKeysResult), "list_keys")]
+[JsonDerivedType(typeof(GetValueOperation), "get_value")]
+public abstract class ActorReadResult
+{
+ /// Prevent external derivations.
+ private protected ActorReadResult()
+ {
+ }
+
+ ///
+ /// Gets the type of the read result operation.
+ ///
+ [JsonIgnore]
+ public abstract ActorReadResultType Type { get; }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadResultType.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadResultType.cs
new file mode 100644
index 0000000000..1fb79a7dba
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorReadResultType.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Specifies the type of actor read result operation.
+///
+public enum ActorReadResultType
+{
+ ///
+ /// Represents a list keys operation result.
+ ///
+ [JsonStringEnumMemberName("list_keys")]
+ ListKeys,
+
+ ///
+ /// Represents a get value operation result.
+ ///
+ [JsonStringEnumMemberName("get_value")]
+ GetValue
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequest.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequest.cs
new file mode 100644
index 0000000000..fa019e1cdd
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequest.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents a request to be sent to an actor.
+///
+public class ActorRequest(ActorId actorId, string messageId, string method, JsonElement @params)
+{
+ ///
+ /// Gets or sets the identifier of the target actor.
+ ///
+ [JsonPropertyName("actorId")]
+ public ActorId ActorId { get; } = actorId;
+
+ ///
+ /// Gets or sets the unique identifier for this request.
+ ///
+ [JsonPropertyName("messageId")]
+ public string MessageId { get; } = messageId;
+
+ ///
+ /// Gets or sets the method name to invoke on the actor.
+ ///
+ [JsonPropertyName("method")]
+ public string Method { get; } = method;
+
+ ///
+ /// Gets or sets the parameters for the method invocation.
+ ///
+ [JsonPropertyName("params")]
+ public JsonElement Params { get; } = @params;
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequestMessage.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequestMessage.cs
new file mode 100644
index 0000000000..fb05194c8c
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequestMessage.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for request messages sent to actors.
+///
+public sealed class ActorRequestMessage(string MessageId) : ActorMessage
+{
+ ///
+ public override ActorMessageType Type => ActorMessageType.Request;
+
+ ///
+ /// Gets or sets the actor ID of the sender.
+ ///
+ [JsonPropertyName("sender")]
+ public ActorId? SenderId { get; init; }
+
+ ///
+ /// Gets or sets the unique identifier for the request.
+ ///
+ [JsonPropertyName("messageId")]
+ public string MessageId { get; } = MessageId;
+
+ ///
+ /// Name of the method to invoke.
+ ///
+ [JsonPropertyName("method")]
+ public string? Method { get; init; }
+
+ ///
+ /// Optional parameters for the method invocation.
+ ///
+ [JsonPropertyName("params")]
+ public JsonElement Params { get; init; }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequestUpdate.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequestUpdate.cs
new file mode 100644
index 0000000000..77e8c301c6
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorRequestUpdate.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+// External (client) interface.
+
+///
+/// Represents an update to an actor request's status and data.
+///
+public class ActorRequestUpdate(RequestStatus status, JsonElement data)
+{
+ ///
+ /// Gets the updated status of the request.
+ ///
+ [JsonPropertyName("status")]
+ public RequestStatus Status { get; } = status;
+
+ ///
+ /// Gets the updated data associated with the request.
+ ///
+ [JsonPropertyName("data")]
+ public JsonElement Data { get; } = data;
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponse.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponse.cs
new file mode 100644
index 0000000000..7a97807faa
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponse.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents a response handle for an actor request, providing access to the result and status updates.
+///
+public class ActorResponse
+{
+ ///
+ /// Gets the identifier of the actor that is processing the request.
+ ///
+ [JsonPropertyName("actorId")]
+ public ActorId ActorId { get; init; }
+
+ ///
+ /// Gets the unique identifier of the message/request.
+ ///
+ [JsonPropertyName("messageId")]
+ public string? MessageId { get; init; }
+
+ ///
+ /// Gets the response data from the actor.
+ ///
+ [JsonPropertyName("data")]
+ public JsonElement Data { get; init; }
+
+ ///
+ /// Gets or sets the current status of the request.
+ ///
+ [JsonPropertyName("status")]
+ public RequestStatus Status { get; init; }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponseHandle.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponseHandle.cs
new file mode 100644
index 0000000000..2508bd28eb
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponseHandle.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents a handle to an actor response, allowing retrieval of the response data and status updates.
+///
+public abstract class ActorResponseHandle
+{
+ ///
+ /// Attempts to get the response from the request if it is immediately available.
+ ///
+ /// When this method returns , contains the actor response; otherwise, .
+ /// if the response is immediately available; otherwise, .
+ ///
+ /// This method does not block and returns immediately. If the request is still pending or processing,
+ /// this method returns .
+ /// Use to wait asynchronously for the response to become available.
+ ///
+ public abstract bool TryGetResponse([NotNullWhen(true)] out ActorResponse? response);
+
+ ///
+ /// Gets the response from the completed request.
+ ///
+ /// A token to cancel the wait operation.
+ /// A task that completes when the request is finished.
+ public abstract ValueTask GetResponseAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Cancels the request if it is still pending.
+ ///
+ /// A task representing the cancellation operation.
+ public abstract ValueTask CancelAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Watches for status and data updates to the request.
+ ///
+ /// A token to cancel the watch operation.
+ /// An asynchronous enumerable of request updates.
+ public abstract IAsyncEnumerable WatchUpdatesAsync(CancellationToken cancellationToken);
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponseMessage.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponseMessage.cs
new file mode 100644
index 0000000000..68510d38e5
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorResponseMessage.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for response messages sent from actors.
+///
+public sealed class ActorResponseMessage(string MessageId) : ActorMessage
+{
+ ///
+ public override ActorMessageType Type => ActorMessageType.Response;
+
+ ///
+ /// Gets or sets the actor ID of the sender.
+ ///
+ [JsonPropertyName("senderId")]
+ public ActorId SenderId { get; init; }
+
+ ///
+ /// Gets or sets the unique identifier for the request.
+ ///
+ [JsonPropertyName("messageId")]
+ public string MessageId { get; } = MessageId;
+
+ ///
+ /// Gets or sets the status of the request.
+ ///
+ [JsonPropertyName("status")]
+ public RequestStatus Status { get; init; }
+
+ ///
+ /// Gets or sets the response data (result or error information).
+ ///
+ [JsonPropertyName("data")]
+ public JsonElement Data { get; init; }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorStateReadOperation.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorStateReadOperation.cs
new file mode 100644
index 0000000000..43e830631a
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorStateReadOperation.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for read operations that query an actor's internal state.
+///
+public abstract class ActorStateReadOperation : ActorReadOperation
+{
+ /// Prevent external derivations.
+ private protected ActorStateReadOperation()
+ {
+ }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorStateWriteOperation.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorStateWriteOperation.cs
new file mode 100644
index 0000000000..f0ec2dcbd8
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorStateWriteOperation.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for write operations that modify an actor's internal state.
+///
+///
+/// This abstract class serves as the foundation for all actor state write operation types.
+/// Each concrete implementation represents a specific type of state modification operation,
+/// such as setting or removing key-value pairs in an actor's state.
+///
+public abstract class ActorStateWriteOperation : ActorWriteOperation
+{
+ /// Prevent external derivations.
+ private protected ActorStateWriteOperation()
+ {
+ }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorType.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorType.cs
index 6f101b46f4..619e018083 100644
--- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorType.cs
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorType.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace Microsoft.Extensions.AI.Agents.Runtime;
@@ -8,6 +10,7 @@ namespace Microsoft.Extensions.AI.Agents.Runtime;
///
/// Represents the type of an actor.
///
+[JsonConverter(typeof(Converter))]
public readonly partial struct ActorType : IEquatable
{
///
@@ -60,9 +63,33 @@ public readonly partial struct ActorType : IEquatable
type is not null && TypeRegex().IsMatch(type);
#if NET
- [GeneratedRegex("^[a-zA-Z_][a-zA-Z_0-9]*$")]
+ [GeneratedRegex("^[a-zA-Z_][a-zA-Z_:0-9]*$")]
private static partial Regex TypeRegex();
#else
- private static Regex TypeRegex() => new("^[a-zA-Z_][a-zA-Z_0-9]*$", RegexOptions.Compiled);
+ private static Regex TypeRegex() => new("^[a-zA-Z_][a-zA-Z_:0-9:]*$", RegexOptions.Compiled);
#endif
+
+ ///
+ /// JSON converter for .
+ ///
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override ActorType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.String)
+ {
+ throw new JsonException("Expected string value for ActorType");
+ }
+
+ string? actorTypeString = reader.GetString() ?? throw new JsonException("ActorType cannot be null");
+ return new ActorType(actorTypeString);
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, ActorType value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.Name);
+ }
+ }
}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperation.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperation.cs
new file mode 100644
index 0000000000..101b3e3c30
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperation.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Base class for all actor write operations that can modify actor state or messaging.
+///
+///
+/// This abstract class serves as the foundation for all actor write operation types.
+/// Each concrete implementation represents a specific type of write operation,
+/// such as modifying actor state or sending messages.
+///
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
+[JsonDerivedType(typeof(SetValueOperation), "set_value")]
+[JsonDerivedType(typeof(RemoveKeyOperation), "remove_key")]
+[JsonDerivedType(typeof(UpdateRequestOperation), "update_request")]
+[JsonDerivedType(typeof(SendRequestOperation), "send_request")]
+public abstract class ActorWriteOperation
+{
+ /// Prevent external derivations.
+ private protected ActorWriteOperation()
+ {
+ }
+
+ ///
+ /// Gets the type of the write operation.
+ ///
+ [JsonIgnore]
+ public abstract ActorWriteOperationType Type { get; }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperationBatch.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperationBatch.cs
new file mode 100644
index 0000000000..7f56e958ad
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperationBatch.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents a batch of write operations to be performed atomically on an actor.
+///
+/// The ETag for optimistic concurrency control.
+/// The collection of write operations to perform.
+public class ActorWriteOperationBatch(string eTag, IReadOnlyCollection operations)
+{
+ ///
+ /// Gets the collection of write operations to perform.
+ ///
+ [JsonPropertyName("operations")]
+ public IReadOnlyCollection Operations { get; } = operations;
+
+ ///
+ /// Gets the ETag for optimistic concurrency control.
+ ///
+ [JsonPropertyName("etag")]
+ public string ETag { get; } = eTag;
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperationType.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperationType.cs
new file mode 100644
index 0000000000..05e5240500
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorWriteOperationType.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Specifies the type of actor write operation.
+///
+public enum ActorWriteOperationType
+{
+ ///
+ /// Represents a set key-value operation.
+ ///
+ [JsonStringEnumMemberName("set_value")]
+ SetValue,
+
+ ///
+ /// Represents a remove key operation.
+ ///
+ [JsonStringEnumMemberName("remove_key")]
+ RemoveKey,
+
+ ///
+ /// Represents a send request operation.
+ ///
+ [JsonStringEnumMemberName("send_request")]
+ SendRequest,
+
+ ///
+ /// Represents an update request operation.
+ ///
+ [JsonStringEnumMemberName("update_request")]
+ UpdateRequest
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/GetValueOperation.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/GetValueOperation.cs
new file mode 100644
index 0000000000..66b4f09e64
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/GetValueOperation.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents a request to read a value from the actor's state by its key.
+///
+/// The key corresponding to the value to read from the actor's state.
+public class GetValueOperation(string key) : ActorStateReadOperation
+{
+ ///
+ /// Gets the key corresponding to the value to read from the actor's state.
+ ///
+ [JsonPropertyName("key")]
+ public string Key { get; } = key;
+
+ ///
+ /// Gets the type of the read operation.
+ ///
+ public override ActorReadOperationType Type => ActorReadOperationType.GetValue;
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/GetValueResult.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/GetValueResult.cs
new file mode 100644
index 0000000000..024a45f441
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/GetValueResult.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents the result of a get value operation containing the retrieved value.
+///
+/// The value retrieved from the actor's state, or null if not found.
+public class GetValueResult(JsonElement? value) : ActorReadResult
+{
+ ///
+ /// Gets the value retrieved from the actor's state.
+ ///
+ [JsonPropertyName("value")]
+ public JsonElement? Value { get; } = value;
+
+ ///
+ /// Gets the type of the read result operation.
+ ///
+ public override ActorReadResultType Type => ActorReadResultType.GetValue;
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActor.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActor.cs
new file mode 100644
index 0000000000..9ba0babf10
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActor.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+// Implemented by the Agent Framework (eg, Agent, Orchestration, Process, etc)
+///
+/// Represents an actor in the actor system that can process messages and maintain state.
+///
+public interface IActor : IAsyncDisposable
+{
+ ///
+ /// Runs the actor.
+ /// When the value returned from this method completes, the actor is considered stopped.
+ /// IActor is expected to call IActorContext.WatchMessagesAsync() to receive messages.
+ ///
+ /// A token to cancel the start operation.
+ /// A task representing the start operation.
+ ValueTask RunAsync(CancellationToken cancellationToken);
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorClient.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorClient.cs
new file mode 100644
index 0000000000..aa51782dc0
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorClient.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Interface for sending requests to actors and managing responses.
+///
+public interface IActorClient
+{
+ ///
+ /// Submits a request to an actor and gets a handle for the response.
+ /// This method is idempotent: if the request is already in progress, it will return the existing response.
+ ///
+ /// The request to send to the actor.
+ /// A token to cancel the operation.
+ /// A task representing the actor response handle.
+ ValueTask SendRequestAsync(ActorRequest request, CancellationToken cancellationToken);
+
+ ///
+ /// Gets an already-running request by its identifier.
+ ///
+ /// The identifier of the actor processing the request.
+ /// The unique identifier of the request message.
+ /// A token to cancel the operation.
+ /// A task representing the actor response handle.
+ ValueTask GetResponseAsync(ActorId actorId, string messageId, CancellationToken cancellationToken);
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorRuntimeBuilder.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorRuntimeBuilder.cs
new file mode 100644
index 0000000000..cc1a310854
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorRuntimeBuilder.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Builder interface for configuring actor types in the runtime.
+///
+public interface IActorRuntimeBuilder
+{
+ ///
+ /// Registers an actor type with its factory method.
+ ///
+ /// The actor type to register.
+ /// The factory method to create instances of the actor.
+ void AddActorType(ActorType type, Func activator);
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorRuntimeContext.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorRuntimeContext.cs
new file mode 100644
index 0000000000..43b87cdcba
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorRuntimeContext.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Provides the runtime context for an actor, enabling it to interact with the actor system.
+///
+public interface IActorRuntimeContext
+{
+ ///
+ /// Gets the identifier of the actor.
+ ///
+ ActorId ActorId { get; }
+
+ ///
+ /// Watches for incoming requests and responses in the actor's inbox and outbox.
+ ///
+ /// A token to cancel the watch operation.
+ /// An asynchronous enumerable of actor notifications.
+ IAsyncEnumerable WatchMessagesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Performs a batch of write operations atomically.
+ ///
+ /// The batch of write operations to perform.
+ /// A token to cancel the operation.
+ /// A task representing the write response.
+ ValueTask WriteAsync(ActorWriteOperationBatch operations, CancellationToken cancellationToken = default);
+
+ ///
+ /// Performs a batch of read operations.
+ ///
+ /// The batch of read operations to perform.
+ /// A token to cancel the operation.
+ /// A task representing the read response.
+ ValueTask ReadAsync(ActorReadOperationBatch operations, CancellationToken cancellationToken = default);
+
+ ///
+ /// Reports progress updates for streaming responses.
+ /// The messageId must correspond to a non-terminated request in the actor's inbox (Status is Pending).
+ ///
+ /// The identifier of the message being updated.
+ /// The sequence number for ordering progress updates.
+ /// The progress data.
+ void OnProgressUpdate(string messageId, int sequenceNumber, JsonElement data);
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorStateStorage.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorStateStorage.cs
new file mode 100644
index 0000000000..1cd51cf074
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/IActorStateStorage.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Interface for actor state storage operations, providing persistence for actor state data.
+///
+public interface IActorStateStorage
+{
+ ///
+ /// Writes state changes to the actor's persistent storage.
+ ///
+ /// The identifier of the actor whose state is being modified.
+ /// The collection of write operations to perform.
+ /// The expected ETag for optimistic concurrency control.
+ /// A token to cancel the operation.
+ /// A task representing the write response with success status and updated ETag.
+ ValueTask WriteStateAsync(ActorId actorId, IReadOnlyCollection operations, string etag, CancellationToken cancellationToken = default);
+
+ ///
+ /// Reads state data from the actor's persistent storage.
+ ///
+ /// The identifier of the actor whose state is being read.
+ /// The collection of read operations to perform.
+ /// A token to cancel the operation.
+ /// A task representing the read response with results and current ETag.
+ ValueTask ReadStateAsync(ActorId actorId, IReadOnlyCollection operations, CancellationToken cancellationToken = default);
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/InMemoryActorStateStorage.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/InMemoryActorStateStorage.cs
new file mode 100644
index 0000000000..2063420ab3
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/InMemoryActorStateStorage.cs
@@ -0,0 +1,384 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Provides an in-memory implementation of for testing and development scenarios.
+///
+///
+///
+/// This implementation stores all actor state in memory using concurrent dictionaries for thread safety.
+/// State is not persisted across application restarts and is lost when the application terminates.
+///
+///
+/// The implementation provides optimistic concurrency control using ETags. Each write operation must
+/// provide the current ETag, and the operation will fail if the ETag has changed since the last read.
+/// This ensures that concurrent modifications to the same actor state are handled correctly.
+///
+///
+/// Supported operations:
+///
+/// - - Sets a key-value pair in the actor's state
+/// - - Removes a key from the actor's state
+/// - - Retrieves a value by key from the actor's state
+/// - - Lists keys in the actor's state with optional prefix filtering
+///
+///
+///
+/// This implementation is suitable for:
+///
+/// - Unit testing scenarios
+/// - Development and prototyping
+/// - Single-process applications where persistence is not required
+///
+///
+///
+/// For production scenarios requiring persistence, consider implementing a custom storage provider
+/// that uses a database or other persistent storage mechanism.
+///
+///
+///
+///
+/// // Create storage instance
+/// var storage = new InMemoryActorStateStorage();
+/// var actorId = new ActorId("MyActor", "instance1");
+///
+/// // Write some state
+/// var writeOps = new List<ActorStateWriteOperation>
+/// {
+/// new SetValueOperation("name", JsonSerializer.SerializeToElement("John")),
+/// new SetValueOperation("age", JsonSerializer.SerializeToElement(30))
+/// };
+/// var writeResult = await storage.WriteStateAsync(actorId, writeOps, "0");
+///
+/// // Read the state back
+/// var readOps = new List<ActorStateReadOperation>
+/// {
+/// new GetValueOperation("name"),
+/// new ListKeysOperation(null), // List all keys
+/// new ListKeysOperation(null, "prefix_") // List keys starting with "prefix_"
+/// };
+/// var readResult = await storage.ReadStateAsync(actorId, readOps);
+///
+///
+public sealed class InMemoryActorStateStorage : IActorStateStorage
+{
+ private static readonly ActivitySource ActivitySource = new("Microsoft.Extensions.AI.Agents.Runtime.Abstractions.InMemoryActorStateStorage");
+
+ private readonly ConcurrentDictionary _actorStates = new();
+ private readonly object _lockObject = new();
+ private long _globalETagCounter = 0;
+
+ ///
+ /// Represents the internal state of an actor including its key-value pairs and ETag.
+ ///
+ private sealed class ActorState
+ {
+ public ConcurrentDictionary Data { get; } = new();
+ public string ETag { get; set; } = "0";
+ }
+
+ ///
+ public ValueTask WriteStateAsync(ActorId actorId, IReadOnlyCollection operations, string etag, CancellationToken cancellationToken = default)
+ {
+ using var activity = ActivitySource.StartActivity("actor.state write");
+
+ if (operations is null)
+ {
+ throw new ArgumentNullException(nameof(operations));
+ }
+
+ if (etag is null)
+ {
+ throw new ArgumentNullException(nameof(etag));
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Set telemetry attributes
+ SetActorAttributes(activity, actorId);
+ SetStateAttributes(activity, "write", operations.Count, etag);
+
+ try
+ {
+ lock (this._lockObject)
+ {
+ var actorState = this._actorStates.GetOrAdd(actorId, _ => new ActorState());
+
+ // Check ETag for optimistic concurrency control
+ if (actorState.ETag != etag)
+ {
+ activity?.SetTag("state.success", false);
+ activity?.SetTag("error.type", "etag_mismatch");
+ activity?.SetStatus(ActivityStatusCode.Error, "ETag mismatch - concurrent modification detected");
+
+ // Return failure with current ETag
+ return new ValueTask(new WriteResponse(actorState.ETag, success: false));
+ }
+
+ // Apply all operations
+ var operationTypes = new List();
+ foreach (var operation in operations)
+ {
+ switch (operation)
+ {
+ case SetValueOperation setValue:
+ actorState.Data[setValue.Key] = setValue.Value;
+ operationTypes.Add("set");
+ break;
+
+ case RemoveKeyOperation removeKey:
+ actorState.Data.TryRemove(removeKey.Key, out _);
+ operationTypes.Add("remove");
+ break;
+
+ default:
+ var errorMessage = $"Unsupported write operation type: {operation.GetType().Name}";
+ var exception = new InvalidOperationException(errorMessage);
+ SetErrorAttributes(activity, exception);
+ throw exception;
+ }
+ }
+
+ // Update ETag
+ var newETag = Interlocked.Increment(ref this._globalETagCounter).ToString();
+ actorState.ETag = newETag;
+
+ // Set success attributes
+ SetOperationStatus(activity, true);
+ activity?.SetTag("state.success", true);
+ activity?.SetTag("state.new_etag", newETag);
+ activity?.SetTag("state.operations", string.Join(",", operationTypes));
+
+ return new ValueTask(new WriteResponse(newETag, success: true));
+ }
+ }
+ catch (Exception ex)
+ {
+ SetErrorAttributes(activity, ex);
+ throw;
+ }
+ }
+
+ ///
+ public ValueTask ReadStateAsync(ActorId actorId, IReadOnlyCollection operations, CancellationToken cancellationToken = default)
+ {
+ using var activity = ActivitySource.StartActivity("actor.state read");
+
+ if (operations is null)
+ {
+ throw new ArgumentNullException(nameof(operations));
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Set telemetry attributes
+ SetActorAttributes(activity, actorId);
+ SetStateAttributes(activity, "read", operations.Count);
+
+ try
+ {
+ var actorState = this._actorStates.GetOrAdd(actorId, _ => new ActorState());
+ var results = new List();
+ var operationTypes = new List();
+
+ foreach (var operation in operations)
+ {
+ switch (operation)
+ {
+ case GetValueOperation getValue:
+ var hasValue = actorState.Data.TryGetValue(getValue.Key, out var value);
+ results.Add(new GetValueResult(hasValue ? value : null));
+ operationTypes.Add($"get:{getValue.Key}");
+ break;
+
+ case ListKeysOperation listKeys:
+ var keys = actorState.Data.Keys.ToList();
+
+ // Filter keys by prefix if provided
+ if (!string.IsNullOrEmpty(listKeys.KeyPrefix))
+ {
+ keys = [.. keys.Where(key => key.StartsWith(listKeys.KeyPrefix, StringComparison.Ordinal))];
+ }
+
+ // Handle pagination if continuation token is provided
+ if (!string.IsNullOrEmpty(listKeys.ContinuationToken))
+ {
+ // For this simple implementation, we'll parse the continuation token as an index
+ if (int.TryParse(listKeys.ContinuationToken, out int startIndex) && startIndex < keys.Count)
+ {
+ keys = [.. keys.Skip(startIndex)];
+ }
+ else
+ {
+ keys = [];
+ }
+ }
+
+ // For simplicity, we'll return all keys without pagination
+ // In a real implementation, you might want to implement proper pagination
+ results.Add(new ListKeysResult(keys.AsReadOnly(), continuationToken: null));
+ operationTypes.Add($"list:{listKeys.KeyPrefix ?? "*"}");
+ break;
+
+ default:
+ var errorMessage = $"Unsupported read operation type: {operation.GetType().Name}";
+ var exception = new InvalidOperationException(errorMessage);
+ SetErrorAttributes(activity, exception);
+ throw exception;
+ }
+ }
+
+ // Set success attributes
+ SetOperationStatus(activity, true);
+ activity?.SetTag("state.etag", actorState.ETag);
+ activity?.SetTag("state.operations", string.Join(",", operationTypes));
+ activity?.SetTag("state.success", true);
+
+ return new ValueTask(new ReadResponse(actorState.ETag, results.AsReadOnly()));
+ }
+ catch (Exception ex)
+ {
+ SetErrorAttributes(activity, ex);
+ throw;
+ }
+ }
+
+ ///
+ /// Clears all stored actor state. This method is primarily intended for testing scenarios.
+ ///
+ public void Clear()
+ {
+ lock (this._lockObject)
+ {
+ this._actorStates.Clear();
+ Interlocked.Exchange(ref this._globalETagCounter, 0);
+ }
+ }
+
+ ///
+ /// Gets the current count of actors that have state stored.
+ ///
+ /// The number of actors with stored state.
+ public int ActorCount => this._actorStates.Count;
+
+ ///
+ /// Gets the current count of keys stored for a specific actor.
+ ///
+ /// The actor identifier.
+ /// The number of keys stored for the specified actor, or 0 if the actor has no state.
+ public int GetKeyCount(ActorId actorId)
+ {
+ return this._actorStates.TryGetValue(actorId, out var state) ? state.Data.Count : 0;
+ }
+
+ ///
+ /// Gets the current ETag for a specific actor.
+ ///
+ /// The actor identifier.
+ /// The current ETag for the specified actor, or "0" if the actor has no state.
+ public string GetETag(ActorId actorId)
+ {
+ return this._actorStates.TryGetValue(actorId, out var state) ? state.ETag : "0";
+ }
+
+ ///
+ /// Sets actor attributes on an activity.
+ ///
+ /// The activity to set attributes on.
+ /// The actor ID.
+ private static void SetActorAttributes(Activity? activity, ActorId actorId)
+ {
+ if (activity == null)
+ {
+ return;
+ }
+
+ activity.SetTag("actor.id", actorId.ToString());
+ activity.SetTag("actor.type", actorId.Type.Name);
+ }
+
+ ///
+ /// Sets state operation attributes on an activity.
+ ///
+ /// The activity to set attributes on.
+ /// The type of state operation.
+ /// Optional count of operations.
+ /// Optional ETag value.
+ private static void SetStateAttributes(Activity? activity, string operationType, int? operationCount = null, string? etag = null)
+ {
+ if (activity == null)
+ {
+ return;
+ }
+
+ activity.SetTag("state.operation.type", operationType);
+
+ if (operationCount.HasValue)
+ {
+ activity.SetTag("state.operation.count", operationCount.Value);
+ }
+
+ if (!string.IsNullOrEmpty(etag))
+ {
+ activity.SetTag("state.etag", etag);
+ }
+ }
+
+ ///
+ /// Sets success/failure status on an activity.
+ ///
+ /// The activity to set status on.
+ /// Whether the operation was successful.
+ /// Optional error message for failures.
+ private static void SetOperationStatus(Activity? activity, bool success, string? errorMessage = null)
+ {
+ if (activity == null)
+ {
+ return;
+ }
+
+ if (success)
+ {
+ activity.SetStatus(ActivityStatusCode.Ok);
+ }
+ else
+ {
+ activity.SetStatus(ActivityStatusCode.Error, errorMessage);
+ }
+ }
+
+ ///
+ /// Sets error attributes on an activity.
+ ///
+ /// The activity to set error attributes on.
+ /// The exception that occurred.
+ private static void SetErrorAttributes(Activity? activity, Exception exception)
+ {
+ if (activity == null)
+ {
+ return;
+ }
+
+ activity.SetTag("error.type", exception.GetType().Name);
+ activity.SetTag("error.message", exception.Message);
+ activity.SetStatus(ActivityStatusCode.Error, exception.Message);
+
+ // Add exception event
+ activity.AddEvent(new ActivityEvent("exception", DateTimeOffset.UtcNow, new ActivityTagsCollection
+ {
+ ["error.type"] = exception.GetType().Name,
+ ["error.message"] = exception.Message,
+ ["error.stack_trace"] = exception.StackTrace
+ }));
+ }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/JsonSerializerExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/JsonSerializerExtensions.cs
new file mode 100644
index 0000000000..5f01bcc57d
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/JsonSerializerExtensions.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Provides extension methods for JSON serialization with source generation support.
+///
+internal static class JsonSerializerExtensions
+{
+ ///
+ /// Gets the JsonTypeInfo for a type, preferring the one from options if available,
+ /// otherwise falling back to the source-generated context.
+ ///
+ /// The type to get JsonTypeInfo for.
+ /// The JsonSerializerOptions to check first.
+ /// The fallback JsonSerializerContext to use if not found in options.
+ /// The JsonTypeInfo for the requested type.
+ public static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options, JsonSerializerContext fallbackContext)
+ {
+ // Try to get from the options first (if a context is configured)
+ if (options.TypeInfoResolver?.GetTypeInfo(typeof(T), options) is JsonTypeInfo typeInfo)
+ {
+ return typeInfo;
+ }
+
+ // Fall back to the provided source-generated context
+ return (JsonTypeInfo)fallbackContext.GetTypeInfo(typeof(T))!;
+ }
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ListKeysOperation.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ListKeysOperation.cs
new file mode 100644
index 0000000000..981a66501d
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ListKeysOperation.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents an operation to list keys from an actor's state, with optional pagination support.
+///
+/// Optional token for pagination to continue listing from a previous operation.
+/// Optional prefix to filter keys. Only keys starting with this prefix will be returned.
+public class ListKeysOperation(string? continuationToken, string? keyPrefix = null) : ActorStateReadOperation
+{
+ ///
+ /// Gets the continuation token for pagination.
+ ///
+ [JsonPropertyName("continuationToken")]
+ public string? ContinuationToken { get; } = continuationToken;
+
+ ///
+ /// Gets the key prefix for filtering. Only keys starting with this prefix will be returned.
+ ///
+ [JsonPropertyName("keyPrefix")]
+ public string? KeyPrefix { get; } = keyPrefix;
+
+ ///
+ /// Gets the type of the read operation.
+ ///
+ public override ActorReadOperationType Type => ActorReadOperationType.ListKeys;
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ListKeysResult.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ListKeysResult.cs
new file mode 100644
index 0000000000..a177aeec59
--- /dev/null
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ListKeysResult.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Extensions.AI.Agents.Runtime;
+
+///
+/// Represents the result of a list keys operation containing the found keys and optional continuation token.
+///
+/// The collection of keys found in the actor's state.
+/// Optional token for pagination to retrieve additional keys.
+public class ListKeysResult(IReadOnlyCollection keys, string? continuationToken) : ActorReadResult
+{
+ ///
+ /// Gets the collection of keys found in the actor's state.
+ ///
+ [JsonPropertyName("keys")]
+ public IReadOnlyCollection Keys { get; } = keys;
+
+ ///
+ /// Gets the continuation token for pagination.
+ ///
+ [JsonPropertyName("continuationToken")]
+ public string? ContinuationToken { get; } = continuationToken;
+
+ ///
+ /// Gets the type of the read result operation.
+ ///
+ public override ActorReadResultType Type => ActorReadResultType.ListKeys;
+}
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.csproj b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.csproj
index 6941c59277..da893dbb4c 100644
--- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.csproj
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.csproj
@@ -5,12 +5,14 @@
$(ProjectsDebugTargetFrameworks)
$(NoWarn);IDE1006;IDE0130
alpha
+ Microsoft.Extensions.AI.Agents.Runtime
true
true
true
+ true
@@ -23,6 +25,10 @@