Skip to content

[Feat]: Enable serving dynamically generated agents #216

@toresenneseth

Description

@toresenneseth

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

There currently seems to be a limitation that you have to know which agent you want to host when the app starts, and you can only host a single one.

This makes the following - very powerful - scenario impossible (from my limited understanding of how the SDK is currently implemented).

Suppose you need to create an enterprise application, or SaaS product, enabling business users to build their own agents using a drag & drop (or similar) UI. Basically AI agents that are generated from configuration, instead of hard-coded by developers using the SDK.

This should be fully possible (as I see it), however, the current implementation of the SDK does not make it possible because you cannot on a pr request basis resolve and execute dynamically generated AI agents based on the url. For example, you currently have to pass in the TaskManager to the MapA2A methods, but the TaskManager.OnMessageReceived does not provide information about the url. This makes sense from a point of view where there's a 1:1 mapping between a TaskManager and agent - which probably is by design.

Describe the solution you'd like

I would like to be able to host dynamically generated AI agents. I want to dynamically serve the agent card, and process the requests, based on the incoming request.

For example, I would like to do something like this:

public static IEndpointConventionBuilder MyMapA2A(this WebApplication app)
{
    app.MapA2A("/a2a/tenants/{tenantId}/workspaces/{workspaceId}/agents/{agentId}");

    var routeGroup = app.MapGroup("/a2a/tenants/{tenantId}/workspaces/{workspaceId}/agents/{agentId}");
    routeGroup.MapGet(".well-known/agent-card.json", async (MyAgentCardService myService, HttpRequest request, CancellationToken cancellationToken) =>
    {
       // quick and dirty hack to remove the well-known part
        var path = request.Path.ToString().Replace("/.well-known/agent-card.json", "");
        var agentUrl = $"{request.Scheme}://{request.Host}{path}";
        var agentCard = await myService.BuildAgentCardAsync(agentUrl, cancellationToken);
        return Results.Ok(agentCard);
    });

    return routeGroup;
}

This is almost possible today, but not quite. If A2AJsonRpcProcessor.ProcessRequestAsync was public, I could create a task manager pr request or agent, and do something like this:

public class DynamicAgentRoutingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IAgentFactory _agentFactory;
    private readonly ConcurrentDictionary<string, ITaskManager> _taskManagers = new();

    public DynamicAgentRoutingMiddleware(
        RequestDelegate next, 
        IAgentFactory agentFactory)
    {
        _next = next;
        _agentFactory = agentFactory;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Extract agent identifier from request
        var agentId = context.Request.RouteValues["agentId"]?.ToString();
        
        if (string.IsNullOrEmpty(agentId))
        {
            await _next(context);
            return;
        }

        // Resolve or create task manager for this agent
        var taskManager = _taskManagers.GetOrAdd(agentId, id =>
        {
            var tm = new TaskManager();
            var agent = _agentFactory.CreateAgent(id);
            agent.Attach(tm);
            return tm;
        });

        // Store task manager in HttpContext for use by endpoints
        context.Items["TaskManager"] = taskManager;
        
        await _next(context);
    }
}

// In Program.cs
app.UseMiddleware<DynamicAgentRoutingMiddleware>();

app.MapPost("/a2a/tenants/{tenantId}/workspaces/{workspaceId}/agents/{agentId}", async (
    HttpContext context,
    HttpRequest request,
    CancellationToken cancellationToken) =>
{
    var taskManager = (ITaskManager)context.Items["TaskManager"]!;
     // Not a public API, so I can't do this
    return await A2AJsonRpcProcessor.ProcessRequestAsync(
        taskManager, 
        request, 
        cancellationToken);
});

Describe alternatives you've considered

No response

Additional context

No response

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