Skip to content

.NET: [Feature]: Update A2AAgent to set a2aMessage.TaskId if present in A2AAgentSession #5417

@lukep-infotrack

Description

@lukep-infotrack

Description

In the A2A Agent, there is a function to create the A2A Message

 private static AgentMessage CreateA2AMessage(A2AAgentSession typedSession, IEnumerable<ChatMessage> messages)
    {
        var a2aMessage = messages.ToA2AMessage();

        // Linking the message to the existing conversation, if any.
        // See: https://github.com/a2aproject/A2A/blob/main/docs/topics/life-of-a-task.md#group-related-interactions
        a2aMessage.ContextId = typedSession.ContextId;

        // Link the message as a follow-up to an existing task, if any.
        // See: https://github.com/a2aproject/A2A/blob/main/docs/topics/life-of-a-task.md#task-refinements
        a2aMessage.ReferenceTaskIds = typedSession.TaskId is null ? null : [typedSession.TaskId];

        return a2aMessage;
    }

I would expect that if I set the TaskId in the A2AAgentSession, that it is assigned to the a2aMessage.TaskId field, indicating that the task needs further updates.

The current implementation implies that if the A2A server returns a taskId, that the task is completed and so subsequent calls its added as the ReferenceTaskId. However that is not correct, as the status of a task may not be terminal and so it can be updated by passing setting the TaskId on the agentMessage.

I propose making it clear on the A2AAgentSession what value you are setting related to a Task, the TaskId or the ReferenceTaskIds so its up to the caller to control.

The work around for me in the mean time, is to not use the MS Agent Framework/A2A integration and just use the A2A SDK directly so I can control how the message is built.

Code Sample

public sealed class A2AAgentSession : AgentSession
{
    internal A2AAgentSession()
    {
    }

    [JsonConstructor]
    internal A2AAgentSession(string? contextId, string? taskId, string[]? referenceTaskIds, AgentSessionStateBag? stateBag) : base(stateBag ?? new())
    {
        this.ContextId = contextId;
        this.TaskId = taskId;
        this.ReferenceTaskIds = referenceTaskIds;
    }

    /// <summary>
    /// Gets the ID for the current conversation with the A2A agent.
    /// </summary>
    [JsonPropertyName("contextId")]
    public string? ContextId { get; internal set; }

    /// <summary>
    /// Gets the ID for the task the agent is currently working on.
    /// </summary>
    [JsonPropertyName("taskId")]
    public string? TaskId { get; internal set; }

    /// <summary>
    /// Gets the IDs for the tasks the agent is currently referencing.
    /// </summary>
    [JsonPropertyName("referenceTaskIds")]
    public string[]? ReferenceTaskIds { get; internal set; }

    /// <inheritdoc/>
    internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
    {
        var jso = jsonSerializerOptions ?? A2AJsonUtilities.DefaultOptions;
        return JsonSerializer.SerializeToElement(this, jso.GetTypeInfo(typeof(A2AAgentSession)));
    }

    internal static A2AAgentSession Deserialize(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null)
    {
        if (serializedState.ValueKind != JsonValueKind.Object)
        {
            throw new ArgumentException("The serialized session state must be a JSON object.", nameof(serializedState));
        }

        var jso = jsonSerializerOptions ?? A2AJsonUtilities.DefaultOptions;
        return serializedState.Deserialize(jso.GetTypeInfo(typeof(A2AAgentSession))) as A2AAgentSession
            ?? new A2AAgentSession();
    }

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string DebuggerDisplay =>
        $"ContextId = {this.ContextId}, TaskId = {this.TaskId}, StateBag Count = {this.StateBag.Count}";
}

then in A2AAgent

 private static AgentMessage CreateA2AMessage(A2AAgentSession typedSession, IEnumerable<ChatMessage> messages)
    {
        var a2aMessage = messages.ToA2AMessage();

        // Linking the message to the existing conversation, if any.
        // See: https://github.com/a2aproject/A2A/blob/main/docs/topics/life-of-a-task.md#group-related-interactions
        a2aMessage.ContextId = typedSession.ContextId;

       a2aMesasage.taskId = typedSession.TaskId;

        // Link the message as a follow-up to an existing task, if any.
        // See: https://github.com/a2aproject/A2A/blob/main/docs/topics/life-of-a-task.md#task-refinements
        a2aMessage.ReferenceTaskIds = typedSession.ReferenceTaskIds is null ? null : typedSession.ReferenceTaskIds ;

        return a2aMessage;
    }

Language/SDK

.NET

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions