microsoft / microsoft/agent-framework

.NET: Automated Type Adapters - Seamlessly Connect Executors and Agents Without Boilerplate

Open
#1,582 3 comments 1 reaction 1 assignee Claimed by @alliscode View on GitHub
.NET workflows
Dominant language
Python
Stars
13.6k
Forks
2.3k
Avg merge
2d 45m
Merged PRs (30d)
358

Description

## Summary

Simplify workflow construction by automatically inserting type adapter executors when connecting components with incompatible types. This eliminates the need for developers to manually create and wire adapter executors (like `StringToChatMessageExecutor`, `JailbreakSyncExecutor`, etc.) when connecting string-based executors to AI agents.

**Value Proposition:**
- 🚀 **less boilerplate code** - No more manual adapter executors
- ✨ **Intuitive API** - Connect executors directly, framework handles the "glue"
- 🐛 **Fewer bugs** - Eliminates common wiring mistakes
- 📊 **Better debugging** - Auto-generated adapters are clearly marked and visualized - if we want to
- 🔧 **Backward compatible** - Opt-in feature, existing code continues to work

---

## Current Issue

### **Problem: Connecting Executors to Agents Requires Manual "Glue" Code**

When building workflows that mix regular executors (which work with simple types like `string`) and AI agents (which expect `ChatMessage` + `TurnToken`), developers must manually create adapter executors to bridge the type gap.

**Example - Current Approach (Verbose):**
```
// 1. Create a custom adapter executor to convert string →
ChatMessage internal sealed class StringToChatMessageExecutor(string id) : Executor(id) { public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) { // Convert string to ChatMessage ChatMessage chatMessage = new(ChatRole.User, message); await context.SendMessageAsync(chatMessage, cancellationToken: cancellationToken);
// Send TurnToken to trigger agent
await context.SendMessageAsync(new TurnToken(emitEvents: true), cancellationToken: cancellationToken);
}
}
// 2. Create another adapter to connect agents together
internal sealed class JailbreakSyncExecutor() : Executor("JailbreakSync") { public override async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default) { string processedContent = ProcessMessage(message); // Custom logic
// Forward to next agent
await context.SendMessageAsync(new ChatMessage(ChatRole.User, processedContent), cancellationToken: cancellationToken);
await context.SendMessageAsync(new TurnToken(emitEvents: true), cancellationToken: cancellationToken);
}
}

// 3. Wire everything together with manual adapters
var workflow = new WorkflowBuilder(userInput) .AddEdge(userInput, stringToChat) // Manual adapter
.AddEdge(stringToChat, jailbreakDetector) // Agent
.AddEdge(jailbreakDetector, jailbreakSync) // Manual adapter
.AddEdge(jailbreakSync, responseAgent) // Agent
.Build();
```
**Problems:**
1. **Boilerplate Explosion** - Every workflow needs these same adapters
2. **Cognitive Overhead** - Developers must understand ChatMessage/TurnToken protocol
3. **Error-Prone** - Easy to forget TurnToken or use wrong ChatRole
4. **Poor Discoverability** - New users don't know they need adapters until runtime errors
5. **Noise in Workflow** - Adapters obscure the actual business logic

---

## Proposed Solution (Simplified)

### **Auto-Insert Type Adapters with Naming Convention + Metadata Tags for Tracking**
Enable `WorkflowBuilder.AddEdge()` to automatically detect type mismatches and inject adapter executors when needed.

**Phase 1: Naming Convention (Immediate - No Schema Changes)**
Use `_Auto_` prefix for auto-generated executors:

**Phase 2: Metadata Enhancement (Optional - Rich Debugging)**
Add metadata tags to `ExecutorInfo` for visualization:

Note: this would make Workflow deserialization-serialization easier-better due to this
---

## Developer Experience Improvements

### **Before (Manual Adapters):**
```
// Create 3 custom adapter executors
var stringToChat = new StringToChatMessageExecutor("Adapter1");
var agentSync = new JailbreakSyncExecutor();
var finalOutput = new FinalOutputExecutor();

// Wire with 6 edges var workflow = new WorkflowBuilder(userInput)
.AddEdge(userInput, stringToChat)
.AddEdge(stringToChat, jailbreakDetector)
.AddEdge(jailbreakDetector, agentSync)
.AddEdge(agentSync, responseAgent)
.AddEdge(responseAgent, finalOutput)
.Build();
```

### **After (Auto-Adapters):**
// Just connect components directly!
var workflow = new WorkflowBuilder(userInput)
.AddEdge(userInput, jailbreakDetector) // Auto-inserts string→ChatMessage adapter
.AddEdge(jailbreakDetector, responseAgent) // Auto-inserts Agent→Agent adapter
.Build();

// Framework auto-generates:
// _Auto_Adapter_userInput_To_jailbreakDetector
// _Auto_Adapter_jailbreakDetector_To_responseAgent

---

## Key Benefits

### **1. Reduced Code**
- **fewer lines** in common scenarios
- **Eliminates ~2-4 adapter classes** per workflow (depends on complexity, though)

### **2. Better Error Messages**
// Before: Cryptic runtime error InvalidOperationException: No handler found for type 'System.String'
// After: Clear error with auto-fix suggestion InvalidOperationException: Type mismatch detected between 'UserInput' (outputs string) and 'JailbreakDetector' (expects ChatMessage).

Suggestion: Enable auto-adaptation with .WithAutoTypeAdaptation()

### **3. Enhanced Visualization**

**Workflow JSON with Metadata:**
`{ "executors": { "UserInput": { "executorId": "UserInput" }, "_Auto_Adapter_UserInput_To_Agent": { "executorId": "_Auto_Adapter_UserInput_To_Agent", "metadata": { "auto-generated": "true", "adapter-type": "StringToAgent", "source": "UserInput", "target": "JailbreakDetector" } }, "JailbreakDetector": { "executorId": "JailbreakDetector" } } }`

## Backward Compatibility

✅ **100% Backward Compatible**
- Opt-in via `WithAutoTypeAdaptation()` (or enabled by default with opt-out)
- Existing manual adapters continue to work
- No breaking changes to public API

---

## Success Metrics

- ✅ Reduced workflow setup code for mixed executor/agent workflows
- ✅ Improve new developer onboarding (measured by sample completion time)
- ✅ Reduce common "type mismatch" issues reported in GitHub issues
- ✅ Maintain 100% backward compatibility (all existing tests pass)

---

## Open Questions

1. **Should auto-adaptation be opt-in or enabled by default?**
- Opt-in: `WithAutoTypeAdaptation()` - safer for existing code
- Default: Enabled by default with `WithoutAutoTypeAdaptation()` opt-out - better DX

2. **Should we provide base classes for common adapter patterns?**
- `StringToAgentExecutor`, `AgentRelayExecutor`, `FanOutExecutor`, `FanInAggregatorExecutor`
- Makes it easy for users to create custom adapters with custom logic

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.