Table of Contents

Delegate Tasks Between Agents with DelegateTool

When a single agent cannot handle every type of request, you can split responsibilities across specialized agents and let a coordinator delegate tasks to them. DelegateTool is a built-in tool that an agent invokes like any other function call: it picks the target agent by name, sends a task description, and receives the result. The coordinator agent decides when and to whom to delegate based on its instructions and the conversation context.

For background on delegation patterns, see the AI Agent Delegation glossary entry.


Why This Matters

Two production problems that delegation solves:

  1. Expertise isolation. A general-purpose assistant that also writes code, reviews documents, and queries databases produces mediocre results in each domain. Dedicated agents with specialized personas and tools perform better. Delegation lets you compose them behind a single entry point.
  2. Context window efficiency. Loading every tool and instruction into one agent consumes context. Delegation keeps each agent lean: the coordinator only needs DelegateTool, while each specialist loads only its own tools.

Prerequisites

Requirement Minimum
.NET SDK 8.0+
VRAM 6+ GB
Model Must support tool calling

Step 1: Create the Project

dotnet new console -n AgentDelegation
cd AgentDelegation
dotnet add package LM-Kit.NET

Step 2: Understand the Delegation Architecture

The delegation system has four main components:

Component Purpose
AgentRegistry Named collection of agents available for delegation
DelegateTool An ITool the coordinator invokes to route tasks
DelegationManager Programmatic delegation with routing, timeouts, and depth control
IDelegationRouter Strategy interface for custom agent selection logic

When the coordinator calls DelegateTool, it passes JSON with agent (target name) and task (the work to do). The tool looks up the agent in the registry, executes the task, and returns the result.


Step 3: Build Specialist Agents

Create agents with distinct personas and capabilities.

using System.Text;
using LMKit.Model;
using LMKit.Agents;
using LMKit.Agents.Tools.BuiltIn;

LMKit.Licensing.LicenseManager.SetLicenseKey("");

Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;

Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("qwen3:8b",
    loadingProgress: p => { Console.Write($"\rLoading: {p * 100:F0}%   "); return true; });
Console.WriteLine("\n");

// Specialist: code writer
Agent codeAgent = Agent.CreateBuilder(model)
    .WithPersona("Code Writer")
    .WithInstruction("You write clean, well-documented C# code. Return only code and brief explanations.")
    .Build();

// Specialist: reviewer
Agent reviewAgent = Agent.CreateBuilder(model)
    .WithPersona("Code Reviewer")
    .WithInstruction("You review code for bugs, security issues, and best practices. Be specific and actionable.")
    .Build();

// Specialist: researcher with web search
Agent researchAgent = Agent.CreateBuilder(model)
    .WithPersona("Researcher")
    .WithInstruction("You research technical topics using web search and provide factual summaries with sources.")
    .WithTools(tools =>
    {
        tools.Register(BuiltInTools.WebSearch);
    })
    .Build();

Step 4: Build the Coordinator with DelegateTool

Register the specialists in an AgentRegistry and enable delegation on the coordinator.

using LMKit.Agents.Delegation;

// Build coordinator with delegation
Agent coordinator = Agent.CreateBuilder(model)
    .WithPersona("Coordinator")
    .WithInstruction(
        "You are a project coordinator. Route tasks to the right specialist:\n" +
        "- 'Code Writer' for writing code\n" +
        "- 'Code Reviewer' for reviewing code\n" +
        "- 'Researcher' for looking up technical information\n" +
        "Answer simple questions yourself. Delegate complex tasks.")
    .WithDelegates(delegates =>
    {
        delegates.Register("Code Writer", codeAgent);
        delegates.Register("Code Reviewer", reviewAgent);
        delegates.Register("Researcher", researchAgent);
    })
    .WithDelegationEnabled()  // Automatically registers DelegateTool
    .Build();

var executor = new AgentExecutor();
AgentExecutionResult result = await executor.ExecuteAsync(coordinator,
    "Write a C# method that validates email addresses with regex, then review it for issues.");
Console.WriteLine(result.Content);

WithDelegationEnabled() automatically creates a DelegateTool from the registry and adds it to the coordinator's tools. The model sees available agent names in the tool's input schema and can call delegate_to_agent with {"agent": "Code Writer", "task": "..."}.


Step 5: Use AgentRegistry for Dynamic Agent Management

AgentRegistry is an enumerable collection with named lookup.

var registry = new AgentRegistry();

// Register by explicit name
registry.Register("Writer", codeAgent);

// Register using the agent's persona as the name
registry.Register(reviewAgent);  // Uses "Code Reviewer" from WithPersona()

// Query
Console.WriteLine($"Registered agents: {registry.Count}");
foreach (string name in registry.Names)
{
    Console.WriteLine($"  - {name}");
}

// Lookup
if (registry.TryGet("Writer", out Agent writer))
{
    Console.WriteLine($"Found: {writer.Identity.Persona}");
}

// Remove
registry.Remove("Writer");

Step 6: Use DelegationManager for Programmatic Control

DelegationManager provides delegation outside the agent loop, with timeout and depth control.

var manager = new DelegationManager(registry)
{
    DefaultTimeout = TimeSpan.FromMinutes(2),
    MaxDelegationDepth = 3  // Prevents infinite delegation chains
};

// Delegate by name
DelegationResult result = await manager.DelegateAsync(
    "Code Reviewer",
    "Review this code for SQL injection:\nvar query = $\"SELECT * FROM users WHERE id = {userId}\";");

if (result.Success)
{
    Console.WriteLine($"Response from {result.AgentName}:");
    Console.WriteLine(result.Response);
}
else
{
    Console.WriteLine($"Delegation failed: {result.Error}");
}

Step 7: Use the Fluent DelegationRequest Builder

For richer delegation context, use DelegationRequest.To() with the builder pattern.

DelegationRequest request = DelegationRequest
    .To("Code Writer", "Implement a rate limiter using the token bucket algorithm")
    .WithContext("Target framework: .NET 8. Must be thread-safe.")
    .WithReason("User requested a production-grade implementation")
    .From("Coordinator");

DelegationResult result = await manager.DelegateAsync(request);
Console.WriteLine($"Success: {result.Success}");
Console.WriteLine($"Duration: {result.Duration.TotalMilliseconds:F0}ms");
Console.WriteLine(result.Response);

Step 8: Monitor Delegations with Events

Both DelegateTool and DelegationManager fire events for logging and interception.

manager.BeforeDelegation += (sender, args) =>
{
    Console.WriteLine($"[Delegating] -> {args.Request.TargetAgentName}: {args.Request.Task}");

    // Optionally cancel
    // args.CancelDelegation("Agent unavailable");
};

manager.AfterDelegation += (sender, args) =>
{
    string status = args.Result.Success ? "OK" : $"FAILED: {args.Result.Error}";
    Console.WriteLine($"[Completed] {args.Result.AgentName} [{args.Result.Duration.TotalMilliseconds:F0}ms] {status}");
};

BeforeDelegationEventArgs lets you modify the request or cancel the delegation entirely. AfterDelegationEventArgs gives you the DelegationResult for logging or metrics.


Step 9: Implement a Custom Router

The default NameBasedDelegationRouter matches agents by name. For smarter routing (e.g., by topic or load), implement IDelegationRouter.

public class TopicRouter : IDelegationRouter
{
    public Task<Agent> SelectAgentAsync(
        DelegationRequest request,
        AgentRegistry availableAgents,
        CancellationToken cancellationToken = default)
    {
        // Route by keywords in the task
        string task = request.Task.ToLower();

        if (task.Contains("review") || task.Contains("check"))
        {
            availableAgents.TryGet("Code Reviewer", out Agent reviewer);
            return Task.FromResult(reviewer);
        }

        if (task.Contains("search") || task.Contains("find") || task.Contains("research"))
        {
            availableAgents.TryGet("Researcher", out Agent researcher);
            return Task.FromResult(researcher);
        }

        // Default to code writer
        availableAgents.TryGet("Code Writer", out Agent writer);
        return Task.FromResult(writer);
    }

    public bool CanHandle(Agent agent, DelegationRequest request) => true;
}

// Use custom router
var manager = new DelegationManager(registry, new TopicRouter());

Share