a2aproject / a2aproject/a2a-dotnet
[Feat]: Enforce required Message fields at compile-time or runtime to ensure A2A spec compliance
- Dominant language
- C#
- Stars
- 262
- Forks
- 64
- Avg merge
- 5d 2h
- Merged PRs (30d)
- 31
Description
### 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):
```typescript
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
```csharp
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)
```csharp
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:
```csharp
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:
```csharp
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
```csharp
///
/// Optional but recommended: Links this message to its parent task.
/// All A2A specification examples include this field.
///
public string? TaskId { get; init; }
```
### Additional context
Used GitHub Copilot for analysis
### Code of Conduct
- [x] I agree to follow this project's Code of Conduct
Contributor guide
Research direction
The issue is about the AgentMessage class and the A2A spec. Start by examining the AgentMessage definition in the codebase, likely in a file like AgentMessage.cs. Review how messages are currently constructed and serialized. The goal is to modify the class to enforce required fields, possibly using the 'required' keyword, and add validation. Check existing tests for message creation to understand the current behavior and update them.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend-api-design
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100