---
url: https://weboost-at.github.io/Typeshift/guide/getting-started.md
description: >-
  Build and run a first agent in four steps, then see how the pieces fit, what
  your app implements, and the docs for coding assistants.
---

# Getting started

Typeshift lets you declare tools and agents in a few lines of C#. In four steps, this page builds an agent that finds
the companies of a city.

## 1. Install

Typeshift needs the .NET 10 SDK and C# 14, its default language version. Add the package to every project that
declares toolsets or agents or calls `AddTypeshift()`. The package carries the source generator, and NuGet does not
pass a generator on to the projects that reference yours, so each of them needs the package itself
([TSH020](./diagnostics.md#tsh020)):

```bash
dotnet add package Typeshift --prerelease
```

The types on this page are in the `Typeshift` namespace, which the package does not import for you, so add
`using Typeshift;` to your files. Step 4 also needs `using Microsoft.Extensions.AI;` and
`using Microsoft.Extensions.DependencyInjection;`, and in a console app the `Microsoft.Extensions.DependencyInjection`
package, which builds the service provider.

Turn on XML docs in a `Directory.Build.props` next to your solution, since tools take their descriptions from them;
without them the build warns with [TSH011](./diagnostics.md#tsh011). The same file sets `TypeshiftIdType`, the type of
the settings ids and conversation ids you pass to runs. Leave it out, and ids are `Guid`s, so step 4 does not compile
with the ids 42 and 1:

```xml
<Project>
  <PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <!-- Ids are Guids unless you name another type. This sample uses int. -->
    <TypeshiftIdType>int</TypeshiftIdType>
  </PropertyGroup>
</Project>
```

## 2. Write a toolset

Steps 2 and 3 each go in a file of their own, and step 4 goes in `Program.cs`. A toolset is a `partial` class, so the
generator can add members to it ([TSH001](./diagnostics.md#tsh001)). It declares its tools in its constructor, and
each tool is a static method:

```cs
internal partial class CompanyToolset : Toolset
{
    public CompanyToolset()
    {
        WithDisplayName("Companies");

        AddTool(FindCompanies)
            .WithSettings<FindCompaniesSettings>()
            .Configurable(s => s.Limit);
    }

    /// <summary>
    /// Finds the companies of a city.
    /// </summary>
    /// <param name="city">The city the companies are based in.</param>
    public static IReadOnlyList<Company> FindCompanies(string city, FindCompaniesSettings settings) =>
    [
        .. Companies
            .Where(company => company.City.Equals(city, StringComparison.OrdinalIgnoreCase))
            .Where(company => company.Active || settings.IncludeInactive)
            .Take(settings.Limit)
    ];

    private static readonly Company[] Companies =
    [
        new("Danube Robotics", "Vienna", Active: true),
        new("Prater Motion", "Vienna", Active: true),
        new("Graz Gearworks", "Graz", Active: false)
    ];
}

internal sealed record Company(string Name, string City, bool Active);
```

* The model calls the tool as `find_companies` and reads its XML docs.
* The model never sees the `FindCompaniesSettings` parameter. Typeshift fills it with the tool's settings when it
  calls the tool.
* `Configurable(s => s.Limit)` lets stored settings and runs change `Limit`, while `IncludeInactive` stays as your
  code sets it.

The settings record sets the defaults:

```cs
internal record FindCompaniesSettings
{
    public int Limit { get; init; } = 5;

    public bool IncludeInactive { get; init; }
}
```

## 3. Write an agent

An agent is a `partial` class too, for the same reason, and derives from `Agent`. It picks a model, its instructions
and its tools:

```cs
internal partial class ResearchAgent : Agent
{
    public ResearchAgent()
    {
        WithDescription("Researches the companies of a city.");

        WithChatOptions()
            .WithModel("openai", "gpt-5-mini")
            .WithTemperature(0.2f)
            .Configurable(o => o.Instructions)
            .Configurable(o => o.Temperature);

        UseToolset<CompanyToolset>();

        ConfigureFindCompanies()
            .WithLimit(3);
    }

    protected override string GetInstructions() =>
        """
        Find companies with the find_companies tool and list them.
        """;
}
```

* `Configurable` lets stored settings and runs change the instructions and the temperature. The model stays as the
  code sets it.
* The generator writes `ConfigureFindCompanies()` because the agent uses `CompanyToolset`, and `WithLimit(3)` sets
  this agent's limit over the record's 5. Configuring a tool the agent does not offer fails the build.

## 4. Run it

`AddTypeshift()` registers every toolset and agent. Chain a store, which keeps settings and conversations, and a chat
client provider, which returns the `IChatClient` that serves each model:

```cs
var services = new ServiceCollection();

services.AddTypeshift()
    .WithStore<InMemoryTypeshiftStore>()
    .WithChatClientProvider<KeyedChatClientProvider>();

services.AddKeyedSingleton<IChatClient>("openai", new CannedChatClient());

await using var provider = services.BuildServiceProvider(validateScopes: true);
```

`CannedChatClient` is a fake client from the docs samples, so they run without a network. In your app, register your
provider's client instead. With the `Microsoft.Extensions.AI.OpenAI` package and an OpenAI API key, that is:

```cs
var openAi = new OpenAIClient(apiKey)
    .GetChatClient("gpt-5-mini")
    .AsIChatClient();

services.AddKeyedSingleton("openai", openAi);
```

`OpenAIClient` is in the `OpenAI` namespace, and `apiKey` comes from your app's configuration, for example an
environment variable. The key `"openai"` is a name you choose: it is the provider the agent names in
`WithModel("openai", "gpt-5-mini")`.
[A real client](./models.md#a-real-client) shows a local server and other providers. `InMemoryTypeshiftStore` comes
with Typeshift and stands in for your database.

`KeyedChatClientProvider` is your `IChatClientProvider`. For each run, Typeshift asks it for the client of the
provider and model the agent names, here `"openai"` and `"gpt-5-mini"`, and it returns the `IChatClient` registered
under the provider's key:

```cs
internal sealed class KeyedChatClientProvider(IServiceProvider services) : IChatClientProvider
{
    // One client per provider serves all its models: Typeshift puts the model into every request
    public ValueTask<IChatClient> GetChatClientAsync(string provider, string model, CancellationToken ct) =>
        ValueTask.FromResult(
            services.GetKeyedService<IChatClient>(provider)
                ?? throw new InvalidOperationException($"No IChatClient is registered for the provider '{provider}'."));
}
```

You configure and run the agent through its host, `AgentHost<ResearchAgent>`, which `AddTypeshift()` registered for
it. The next snippet starts by creating a scope and resolving the host from it. In a web app, you take the host as a
constructor parameter of your controller or service instead ([Using an agent](./using-an-agent.md)).

A settings id is an id you choose, for example one per customer. The store keeps the agent's settings under it, and
every run that passes the id uses them. `ConfigureAsync` loads the agent's generated `ResearchAgent.Settings` record,
which holds only what the code marked `Configurable`, applies your change and saves it:

```cs
await using var scope = provider.CreateAsyncScope();
var research = scope.ServiceProvider.GetRequiredService<AgentHost<ResearchAgent>>();
var settingsId = 42;

await research.ConfigureAsync(settingsId, settings => settings with
{
    Temperature = 0.4f,
    FindCompanies = settings.FindCompanies with { Limit = 2 }
});
```

Every run with settings id 42 now finds at most 2 companies. Both ids are optional: a run without a settings id uses
the agent's code alone, and a run with neither id needs no store. With the conversation id, Typeshift stores the run
under conversation 1, and a later run with the same id continues the conversation:

```cs
var answer = await research.RunAsync("Vienna", settingsId, conversationId: 1);

Console.WriteLine($"{research.Agent.Name}: {answer.Text}");
```

The limit passes through three layers: 5 in the record, 3 in the agent and 2 in the stored settings. Each layer
overrides the one before, so the stored 2 applies. A run can pass settings of its own, a fourth layer over the stored
ones, as [Settings layering](./settings-and-stores.md#settings-layering) explains.

## How the pieces fit

```mermaid
flowchart LR
  subgraph compile [Compile time]
    toolset[Toolset] --> generator[Source generator]
    agent[Agent] --> generator
    generator --> functions[An AIFunction per tool]
    generator --> settings[A Settings record per agent]
    generator --> registration["AddTypeshift()"]
  end
  subgraph runtime [Runtime]
    host["AgentHost&lt;TAgent&gt;"] --> provider[Your IChatClientProvider]
    provider --> client[The model's IChatClient]
    host --> store[Your store]
  end
  registration --> host
```

`AddTypeshift()` registers an `AgentHost<TAgent>` per agent. The host resolves the settings, calls the model, runs the
tools and stores the conversation.

| Piece | What it is | Read |
|---|---|---|
| Toolset | A `partial` class deriving from `Toolset` that declares the tools of one domain | [Toolsets](./toolsets.md) |
| Tool settings | A record of values a tool needs that the model must not choose | [Tool settings](./tool-settings.md) |
| Agent | A `partial` class deriving from `Agent`, with chat options, instructions and tools | [Agents](./defining-agents.md) |
| Agent host | `AgentHost<TAgent>`, which runs one agent in the scope it was resolved from | [Using an agent](./using-an-agent.md) |
| Store | Your `ITypeshiftStore`, or an `ISettingsStore` and an `IConversationStore`, which keep settings and conversations | [Settings and stores](./settings-and-stores.md) |

The [glossary](./glossary.md) defines every other term.

## What your app implements

Your app implements a few interfaces. Every run needs a chat client provider, and the others come with the feature
that uses them:

| Interface | Needed for | Read |
|---|---|---|
| `IChatClientProvider` | Every run. It returns the `IChatClient` for a provider and a model. | [Models and providers](./models.md) |
| `ITypeshiftStore`, or `ISettingsStore` and `IConversationStore` | Settings ids and conversation ids. A singleton; `InMemoryTypeshiftStore` serves tests and samples. | [Implementing a store](./implementing-a-store.md) |
| `IContextProvider` | Memory, documents or other data added to every run of an agent. | [Context providers](./middleware.md#context-providers) |
| `IToolApprovalRule`, `IRunMiddleware`, `IToolMiddleware`, `IMcpToolProvider` | Approval rules, middleware and MCP servers. | [The chain](./registration.md#the-chain) |

## Docs for coding assistants

The guide is also published as Markdown for coding assistants such as GitHub Copilot, Claude Code or Cursor. Point
your assistant at one of these files, so it looks up the API these pages describe instead of guessing from what it
learned in training:

| File | Holds |
|---|---|
| [`https://weboost-at.github.io/Typeshift/llms.txt`](https://weboost-at.github.io/Typeshift/llms.txt) | The guide's pages, each with a link and its one-line description |
| [`https://weboost-at.github.io/Typeshift/llms-full.txt`](https://weboost-at.github.io/Typeshift/llms-full.txt) | Every page of the guide in one file |
| A guide page's address with `.md`, such as [`toolsets.md`](https://weboost-at.github.io/Typeshift/guide/toolsets.md) | That page alone |

`llms.txt` suits an assistant that fetches pages as it needs them, because it then reads only the pages of the task
at hand. Add `llms-full.txt` to the context of one that cannot fetch. `llms.txt` follows the
[llms.txt](https://llmstxt.org) format, and all three are rebuilt with the site, so they match the pages you read here.

## Related

* [Toolsets](./toolsets.md): tools, their names, parameters and results in depth.
* [Tool settings](./tool-settings.md): what a settings record may hold and what may change it.
* [Agents](./defining-agents.md): everything an agent's constructor can declare.
* [Using an agent](./using-an-agent.md): the host in a controller, an endpoint or a background job.
* [Running agents](./running-agents.md): real chat clients, structured output and streaming.
* [Conversations](./conversations.md): resuming, steering and watching runs.
* [Glossary](./glossary.md): the words these docs use.
