// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Hosting; /// /// Defines the contract for storing and retrieving agent conversation threads. /// /// /// /// Implementations of this interface enable persistent storage of conversation threads, /// allowing conversations to be resumed across HTTP requests, application restarts, /// or different service instances in hosted scenarios. /// /// /// Trust model. The conversationId passed to /// and typically originates /// from the wire (for example, an AG-UI RunAgentInput.ThreadId or an A2A /// contextId). It is a chain-resume identifier, not an authorization /// token, and the (agent, conversationId) tuple carries no principal/owner /// dimension. Hosts that serve more than one user from the same registered store must /// therefore compose a principal dimension into the lookup key, otherwise any caller /// who knows or guesses another caller's conversationId can resume /// that other caller's persisted thread. The framework provides /// as a decorator that rewrites /// conversationId to include an isolation key resolved from a /// (for example, the ASP.NET Core /// ClaimsIdentitySessionIsolationKeyProvider wired up via /// UseClaimsBasedSessionIsolation(...)). When no provider is registered, the /// store behaves as a single-namespace persistence layer — appropriate for /// single-user / first-run / prototyping scenarios but unsafe for multi-user hosts. /// /// /// Implementer guidance. Implementations should treat /// conversationId as opaque: do not parse it, do not impose length /// or character-set constraints on it, and do not assume it round-trips to the value /// the caller originally supplied (decorators such as /// may rewrite it before forwarding). /// Be aware that any logging, telemetry, or audit sink that surfaces /// conversationId will also surface the isolation prefix when a /// scoping decorator is in the chain. /// /// public abstract class AgentSessionStore { /// /// Saves a serialized agent session to persistent storage. /// /// The agent that owns this session. /// The unique identifier for the conversation/session. /// The session to save. /// The to monitor for cancellation requests. /// A task that represents the asynchronous save operation. public abstract ValueTask SaveSessionAsync( AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default); /// /// Retrieves a serialized agent session from persistent storage. /// /// The agent that owns this session. /// The unique identifier for the conversation/session to retrieve. /// The to monitor for cancellation requests. /// /// A task that represents the asynchronous retrieval operation. /// The task result contains the serialized session state, or if not found. /// public abstract ValueTask GetSessionAsync( AIAgent agent, string conversationId, CancellationToken cancellationToken = default); /// Asks the for an object of the specified type . /// The type of object being requested. /// An optional key that can be used to help identify the target service. /// The found object, otherwise . /// is . /// /// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the , /// including itself or any services it might be wrapping. This is particularly useful for inspecting delegation chains /// to verify that specific store implementations are present. /// public virtual object? GetService(Type serviceType, object? serviceKey = null) { _ = Throw.IfNull(serviceType); return serviceKey is null && serviceType.IsInstanceOfType(this) ? this : null; } /// Asks the for an object of type . /// The type of the object to be retrieved. /// An optional key that can be used to help identify the target service. /// The found object, otherwise . /// /// The purpose of this method is to allow for the retrieval of strongly typed services that may be provided by the , /// including itself or any services it might be wrapping. This is particularly useful for inspecting delegation chains /// to verify that specific store implementations are present. /// public TService? GetService(object? serviceKey = null) => this.GetService(typeof(TService), serviceKey) is TService service ? service : default; }