Skip to content

[Feat]: Enforce required Message fields at compile-time or runtime to ensure A2A spec compliance #198

@dasiths

Description

@dasiths

Is your feature request related to a problem? Please describe.

The A2A specification (v0.3.0) defines Message.messageId as a required field (Section 6.4):

export interface Message {
  readonly role: "user" | "agent";
  parts: Part[];
  messageId: string;             // Required - must be a UUID
  taskId?: string;              // Optional
  contextId?: string;           // Optional
  kind: "message";
}

However, the current C# SDK allows developers to construct AgentMessage (or Message) objects without providing a messageId, which results in spec non-compliant responses that may be rejected by clients or cause interoperability issues.

Example of Current Problematic Code

await _taskManager.UpdateStatusAsync(taskId, TaskState.InputRequired, new AgentMessage()
{
    Parts = [new TextPart() { Text = "When ready say go ahead" }],
},
cancellationToken: cancellationToken);

This compiles and runs without errors, but produces a message with a missing or empty messageId, violating the A2A spec.

Impact

  • Spec non-compliance: Messages without messageId violate the A2A protocol
  • Interoperability issues: Clients expecting spec-compliant messages may reject responses
  • Developer confusion: No indication that a required field is missing until runtime testing
  • Inconsistent examples: Some SDK examples (like EchoAgent.cs) include MessageId, while developers writing new agents may forget to include it

Describe the solution you'd like

Add compile-time or runtime enforcement for required Message fields. Consider one or more of these approaches:

Option 1: Use C# 11 required Keyword (Preferred)

public class AgentMessage
{
    public required string MessageId { get; init; }
    public required MessageRole Role { get; init; }
    public required Part[] Parts { get; init; }
    public string? TaskId { get; init; }
    public string? ContextId { get; init; }
    public string Kind { get; init; } = "message";
}

Benefits:

  • ✅ Compile-time errors when required fields are missing
  • ✅ Clear intent in the type system
  • ✅ IDE support (IntelliSense shows required fields)
  • ✅ No runtime overhead

Requirement: Requires C# 11+ (.NET 7+), which the SDK already targets (.NET 8/9)

Describe alternatives you've considered

Option 2: Runtime Validation

Add validation in the TaskManager or serialization layer:

private void ValidateMessage(AgentMessage message)
{
    if (string.IsNullOrWhiteSpace(message.MessageId))
    {
        throw new ArgumentException(
            "Message.MessageId is required by A2A specification (Section 6.4). " +
            "Assign a unique identifier using: MessageId = Guid.NewGuid().ToString()",
            nameof(message));
    }
    
    // Validate other required fields...
}

Option 3: Builder Pattern with Validation

Provide a fluent builder that ensures required fields:

var message = AgentMessage.Create()
    .WithMessageId(Guid.NewGuid().ToString())  // Required
    .WithRole(MessageRole.Agent)                // Required
    .WithParts(new TextPart { Text = "..." })   // Required
    .WithTaskId(taskId)                          // Optional
    .WithContextId(contextId)                    // Optional
    .Build();

Option 4: Analyzer Package (Advanced)

Create a Roslyn analyzer that warns at compile-time when MessageId is not set during object initialization.

Recommended Approach

Combination of Option 1 + Option 2:

  1. Use required keyword for compile-time safety (Option 1)
  2. Add runtime validation as a safety net (Option 2)
  3. Update all SDK examples and documentation to show the correct pattern

This provides defense-in-depth and matches the strictness of the Node.js SDK while leveraging C#'s type system.

Additional Recommendations

Also Consider for Optional But Recommended Fields

While taskId and contextId are optional per the spec schema, all official spec examples include them (Section 9.4). Consider:

  • Adding XML documentation comments indicating these are "optional but recommended"
  • Providing helper methods that make it easy to include them
  • Adding analyzer warnings (not errors) when they're omitted
/// <summary>
/// Optional but recommended: Links this message to its parent task.
/// All A2A specification examples include this field.
/// </summary>
public string? TaskId { get; init; }

Additional context

Used GitHub Copilot for analysis

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions