ToolsDefining tools

Defining tools

Define typed Runiq tools with C# input and output models.

A Runiq tool implements IRuniqTool<TInput, TOutput>.

C#
public interface IRuniqTool<TInput, TOutput>
{
    Task<TOutput> ExecuteAsync(
        TInput input,
        CancellationToken cancellationToken = default);
}

TInput is the structured input the model must provide. TOutput is the structured result returned by your .NET code.

Add metadata

Every tool class must be decorated with RuniqToolAttribute.

C#
[RuniqTool(
    name: "weather",
    description: "Returns deterministic demo weather guidance for a city.")]
public sealed class WeatherTool : IRuniqTool<WeatherInput, WeatherOutput>
{
    public Task<WeatherOutput> ExecuteAsync(
        WeatherInput input,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(input);

        var city = string.IsNullOrWhiteSpace(input.City)
            ? "City"
            : input.City.Trim();

        return Task.FromResult(
            new WeatherOutput(
                City: city,
                TemperatureCelsius: 22,
                Condition: "Partly Cloudy",
                Advice: "Suitable for walking with a light jacket."));
    }
}

public sealed record WeatherInput(string City);

public sealed record WeatherOutput(
    string City,
    int TemperatureCelsius,
    string Condition,
    string Advice);

The name is the runtime tool name. The description tells the model when this tool is useful.

WeakBetter
Weather tool.Returns deterministic demo weather guidance for a city.
Searches things.Searches customer support articles by query text.
Budget helper.Estimates an approximate travel budget from city, day count, group size, and style.

Type rules

Runiq validates tool types when they are registered.

RuleWhy it matters
Tool type must be a concrete class.The runtime must be able to instantiate it.
Tool type must implement IRuniqTool<TInput, TOutput>.The runtime needs a typed input and output contract.
Tool type must implement only one IRuniqTool<,> interface.The runtime must know exactly which contract to expose.
Tool type must have RuniqToolAttribute.The model and dashboard need a name and description.
Tool names must be unique across registered tool types.Runtime metadata and model tool definitions need unambiguous names.

Input models

Use small, explicit input models.

Good:

C#
public sealed record TravelBudgetEstimateInput(
    string City,
    int DayCount,
    int GroupSize,
    string TravelStyle);

Weak:

C#
public sealed record ToolInput(string Data);

Clear property names help the model produce correct tool arguments and help the dashboard render the input shape.

Input types can be records or classes. Public readable properties are used to build dashboard schema metadata. For tools that do not need input, use EmptyToolInput.

C#
public sealed class ServerTimeTool : IRuniqTool<EmptyToolInput, ServerTimeOutput>
{
    public Task<ServerTimeOutput> ExecuteAsync(
        EmptyToolInput input,
        CancellationToken cancellationToken = default)
    {
        return Task.FromResult(
            new ServerTimeOutput(DateTimeOffset.UtcNow));
    }
}

public sealed record ServerTimeOutput(DateTimeOffset UtcNow);

Output models

Return structured data, not a long paragraph.

C#
public sealed record PlacesOutput(
    string City,
    IReadOnlyList<PlaceSuggestion> Places);

public sealed record PlaceSuggestion(
    string Name,
    string Area,
    int EstimatedMinutes,
    string Note);

The final user-facing prose should usually be produced by the agent after the tool result returns to the model. The tool result itself is intermediate structured data.

Dependency injection

Tools are created through ASP.NET Core dependency injection using ActivatorUtilities.

C#
[RuniqTool(
    name: "customer_subscription",
    description: "Looks up the current subscription state for a customer.")]
public sealed class CustomerSubscriptionTool
    : IRuniqTool<CustomerSubscriptionInput, CustomerSubscriptionOutput>
{
    private readonly ICustomerRepository customers;

    public CustomerSubscriptionTool(ICustomerRepository customers)
    {
        this.customers = customers;
    }

    public async Task<CustomerSubscriptionOutput> ExecuteAsync(
        CustomerSubscriptionInput input,
        CancellationToken cancellationToken = default)
    {
        var subscription = await customers.GetSubscriptionAsync(
            input.CustomerId,
            cancellationToken);

        return new CustomerSubscriptionOutput(
            input.CustomerId,
            subscription.PlanName,
            subscription.IsActive);
    }
}

public sealed record CustomerSubscriptionInput(string CustomerId);

public sealed record CustomerSubscriptionOutput(
    string CustomerId,
    string PlanName,
    bool IsActive);

This is the native .NET path: keep repositories, services, HTTP clients, database contexts, validators, and domain rules in your application and expose only the safe, typed capability to the agent.

Existing application code

You do not need to redesign the application around the agent. In Runiq.ExpenseDesk, the tool receives ExpenseDeskDatabase from DI and uses normal SQL filtering over application-owned tables. In a production app, the same pattern can wrap EF Core repositories, Dapper queries, HTTP clients, domain services, or policy engines.

Keep the tool contract small. Let the tool expose a safe operation such as "search expenses" or "lookup subscription state"; keep lower-level implementation details inside your existing .NET services.

On this page