AgentsPatterns

Patterns

Organize Runiq agents cleanly in a .NET application.

Keep agents close to the behavior they define.

For a real application, create an Agents folder and put each agent definition in its own file. Program.cs should register agents; it should not become the place where long instructions, tool wiring, and behavior rules accumulate.

An agent is application code. Treat it like a registered runtime component with identity, model settings, instructions, tools, optional Context Spaces, and dashboard metadata.

Text
MyApp/
  Program.cs
  Agents/
    SupportAgent.cs
    BillingAgent.cs
    PolicyReviewAgent.cs
  Tools/
    CustomerSubscriptionTool.cs
    CreateSupportTicketTool.cs
    PolicyLookupTool.cs
  Context/
    ProductSupportContext.cs
  Skills/
    support/
      SKILL.md

For the travel workflow sample:

Text
Runiq.WorkflowTravelPlanner/
  Program.cs
  Agents/
    WeatherAgent.cs
    PlacesAgent.cs
    PlannerAgent.cs
  Tools/
    WeatherTool.cs
    PlacesTool.cs
    MealSuggestionTool.cs
    BudgetEstimatorTool.cs
  Workflows/
    TravelPlanningWorkflow.cs

Travel is only the sample domain. The pattern is the important part: agents live in Agents/, tools live in Tools/, workflows live in Workflows/, and startup code only wires them together.

Keep Program.cs small

C#
using Runiq.Core;
using Runiq.WorkflowTravelPlanner.Agents;

var builder = WebApplication.CreateBuilder(args);

var openAiApiKey = builder.Configuration["OpenAI:ApiKey"];

builder.Services.AddRuniqServer(options =>
{
    options.AddAgent(WeatherAgent.Create(openAiApiKey));
    options.AddAgent(PlacesAgent.Create(openAiApiKey));
    options.AddAgent(PlannerAgent.Create(openAiApiKey));
});

var app = builder.Build();

app.UseRuniqDashboard(options =>
{
    options.Path = "/dashboard";
    options.Title = "Runiq Workflow Travel Planner";
});

app.Run();

The startup file now answers one question: which Runiq runtime components are registered?

It does not hide long prompts or tool rules inline. To understand Weather Agent, open Agents/WeatherAgent.cs. To understand Planner Agent, open Agents/PlannerAgent.cs.

Put each agent in its own file

Use a small agent class or static factory per agent.

C#
using Runiq.Agents;
using Runiq.Agents.Tools;
using Runiq.WorkflowTravelPlanner.Tools;

namespace Runiq.WorkflowTravelPlanner.Agents;

public sealed class WeatherAgent : Agent
{
    private WeatherAgent(string? apiKey)
        : base(
            id: "weather-agent",
            name: "Weather Agent",
            instructions: """
            You are the Weather Analyst in a deterministic travel planning workflow.

            Your responsibility:
            - Analyze weather and travel comfort only.
            - Always use WeatherTool when the request involves a city or trip plan.
            - After using WeatherTool, write a short natural-language contribution.

            Boundaries:
            - Do not create an itinerary.
            - Do not write the final travel plan.
            - Do not print raw JSON.
            """,
            model: "openai/gpt-5",
            apiKey: apiKey)
    {
    }

    public static Agent Create(string? apiKey)
    {
        return new WeatherAgent(apiKey)
            .AddTool<WeatherTool>();
    }
}

The private constructor makes callers use Create(...), so the tool attachment is not accidentally skipped. The factory returns a complete agent definition: identity, instructions, model, provider key, and allowed tools.

Group by responsibility

Prefer focused agents over one large general agent.

In the travel workflow sample, Weather Agent handles weather and comfort, Places Agent handles place suggestions and route considerations, and Planner Agent creates the final itinerary. This keeps each instruction set small and each dashboard run easier to inspect.

The same pattern works outside travel:

Text
SupportAgent        answers customer support questions
BillingAgent        handles billing-specific explanations
PolicyReviewAgent   compares requests against policy context
IncidentAgent       summarizes incident timelines and follow-ups

Each agent should have a clear job, a stable id, and only the tools or Context Spaces it needs.

Use registration helpers for larger apps

As the app grows, you can move grouped registration into an application-owned extension method.

C#
using Runiq.Core.Configuration;
using Runiq.WorkflowTravelPlanner.Agents;

namespace Runiq.WorkflowTravelPlanner;

public static class RuniqAgentRegistration
{
    public static RuniqServerOptions AddTravelPlannerAgents(
        this RuniqServerOptions options,
        string? openAiApiKey)
    {
        options.AddAgent(WeatherAgent.Create(openAiApiKey));
        options.AddAgent(PlacesAgent.Create(openAiApiKey));
        options.AddAgent(PlannerAgent.Create(openAiApiKey));

        return options;
    }
}

Then Program.cs becomes:

C#
builder.Services.AddRuniqServer(options =>
{
    options.AddTravelPlannerAgents(openAiApiKey);
});

This is not required by Runiq. It is a useful project organization pattern when the number of agents grows.

Keep tools and context outside agent files

Agent files should describe behavior and allowed capabilities. Tool files should implement deterministic .NET operations. Context Space files should define source-backed knowledge boundaries.

Do not put database queries, HTTP calls, policy calculations, or document content directly into agent instructions. Put application behavior in tools and domain knowledge in Context Spaces, then attach only what the agent should use.

Common mistakes

Putting every agent inline in Program.cs is fine for a quick spike, but it becomes hard to review quickly. Move real agents to Agents/*.cs.

Giving one agent every responsibility makes debugging harder. Split agents when the responsibilities are naturally different.

Attaching every tool to every agent weakens the capability boundary. Attach only the tools that agent is allowed to use.

Treating the tool result as the final answer is also a common mistake. Tool results go back to the model so the agent can continue and produce the final response.

On this page