Skip to content

Tool settings ​

Some values a tool needs are not the model's to choose, such as how many tickets a search returns. They go in a settings record that the tool receives and the model never sees.

The settings record ​

A settings record holds values a tool needs. Its parameterless constructor sets the code defaults, here through property initializers:

cs
internal record SearchTicketsSettings
{
    public int Limit { get; init; } = 10;

    public bool IncludeClosed { get; init; }

    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(5);
}

snippet source | anchor

It must be a record (TSH005). Settings resolve in layers, from these code defaults up to a single run, and each layer applies its values to a with copy of the defaults.

Configurable members ​

A configurable member is one that stored settings and runs may change. You choose which members are configurable, and you can declare that from either side. WithSettings<T>() keeps every member in code unless Configurable(...) names it:

cs
AddTool(SearchTickets)
    .ReadOnly()
    .WithSettings<SearchTicketsSettings>()
    .Configurable(s => s.IncludeClosed)
    .Configurable(s => s.Limit);

snippet source | anchor

WithConfigurableSettings<T>() opens every settable member unless NotConfigurable(...) names it:

cs
AddTool(SearchArticlesAsync)
    .WithConfigurableSettings<SearchArticlesSettings>()
    .NotConfigurable(s => s.Limit);

snippet source | anchor

The difference shows when you add a member to the record later. Under WithSettings<T>() it stays in code until you name it. Under WithConfigurableSettings<T>(), stored settings and runs can change it right away, without another code change.

  • WithSettings<T>() takes only Configurable after it, and WithConfigurableSettings<T>() only NotConfigurable.
  • The generator reads each lambda from your source and never runs it, so each one must be a plain member access, s => s.Member (TSH015).
  • Under WithConfigurableSettings<T>(), members marked [Obsolete] stay in code without a NotConfigurable(...).
  • A configurable member needs a public set or init accessor (TSH013).

A renamed member loses its stored values

Stored settings keep each member under its name. Rename or remove a configurable member, and its stored value reads as missing, so the layer below applies. When that matters, raise the schema version the agent passes to WithSchemaVersion and migrate the stored documents, as Migrate older settings explains.

Storable types ​

A configurable member needs a type settings can store (TSH012):

  • strings, Booleans, enums (stored by name), Guid, DateTime, DateTimeOffset, TimeSpan, DateOnly, TimeOnly, Uri and ReasoningOptions;
  • the numbers byte, short, int, long, float, double and decimal;
  • non-generic classes and records with a public parameterless constructor, no required members and storable settable members, replaced as a whole and never merged, and record structs that meet the same rules;
  • arrays, lists and sets of storable values, and dictionaries keyed by string or an enum.

Items and dictionary values cannot be nullable value types, and a type that contains itself is not storable. Other structs are not storable, because most keep their state in fields that no public setter writes.

Receiving settings in the tool ​

The tool takes a parameter of its settings type, and Typeshift fills it with the resolved settings when it calls the tool. The analyzer reports a tool that has settings but no such parameter (TSH010), since its settings would reach nothing:

cs
/// <summary>
/// Searches the customer's support tickets, newest first.
/// </summary>
/// <param name="query">Words the ticket's title contains.</param>
public static IReadOnlyList<Ticket> SearchTickets(
    string query,
    SearchTicketsSettings settings,
    IServiceProvider services) =>
    services.GetRequiredService<ITicketClient>().Search(query, settings.IncludeClosed, settings.Limit);

snippet source | anchor

Toolsets lists the other parameters the call fills.

The generated overrides ​

For a tool with configurable members, the generator writes a {Tool}Overrides record into the toolset, such as TicketToolset.SearchTicketsOverrides, with each configurable member as nullable. Stored settings and run settings set a tool's values through this record, and a null keeps the value of the layer below. Generated members shows it in full.

The layers ​

A tool's settings resolve through four settings layers: the record's code defaults, the agent's Configure{Tool}(), the stored settings and the run's settings. The last two set values through the overrides, so they can change only the configurable members. The agent's Configure{Tool}() can set every settable member.

  • Toolsets: declaring the tools that take settings.
  • Settings layering: which layer wins, and what a layer cannot do.
  • Agents: setting a tool's settings in one agent.
  • Builders: every call of a tool's declaration.

Released under the MIT License.