# Write your own agent source

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/guides/write-your-own-agent-source/
> Summary: Add agents from Git, another database, or an external runtime through IAgentSource without bypassing Tracon's catalog and run pipeline.

`IAgentSource` is the catalog extension point. Use it when agent definitions do not
live in the management database, or when an agent is backed by another runtime.
Register it through `AddAgentSource()`; the catalog keeps the normal decorator chain,
so recording, telemetry, and approval behavior still apply.

A complete, buildable definition source lives in
`samples/Tracon.Samples.CustomAgentSource` — it reads `AgentDefinition` JSON files
from a directory, uses only published packages (`PackageReference`, not a
project-internal type), and its test project runs the official contract suite plus a
real agent run. Read its source alongside this guide.

`AddAgentSource` has three overloads. The generic one resolves the source's own
constructor dependencies (including its own settings, registered separately) through DI:

```csharp
builder.Services.AddSingleton(new GitAgentSourceOptions { RepositoryUrl = url });
builder.AddTracon()
       .AddAgentSource<GitAgentSource>();
```

Pass a ready instance, or a factory, when the source needs something that does not
belong in the container as its own singleton:

```csharp
builder.AddTracon().AddAgentSource(sp => new GitAgentSource(repositoryPath, sp.GetRequiredService<AgentDefinitionCompiler>()));
```

All three register the source as a **singleton**; calling the generic overload twice
for the same type registers it once.

## Choose the source shape

| Shape | Use it when | Resolve behavior |
|---|---|---|
| Definition source | You own an `AgentDefinition` in Git, object storage, or another database | Call `AgentDefinitionCompiler.CompileCachedAsync()` |
| Agent source | You already own a runnable MAF `AIAgent` | Return the agent directly |

For a definition source, let the compiler own dependency fingerprints and the BYOK
cache bypass. Do not repeat that logic in your source.

```mermaid
flowchart LR
    accTitle: The two source shapes
    accDescr: A definition source hands an AgentDefinition to the compiler, which builds the agent with the model rings, the tenant key and the cost. An agent source builds its own agent; it gets the same model rings, tenant key and egress policy from CreateChatClientAsync, and the cost from the model named in its descriptor. The catalog applies Tracon's decorators to both.
    DEF["Definition source<br/>AgentDefinition"] --> COMP["AgentDefinitionCompiler<br/>CompileCachedAsync"]
    COMP --> CAT["Catalog decorators"]
    OWN["Agent source<br/>your AIAgent"] --> CLIENT["CreateChatClientAsync<br/>rings · tenant key · egress"]
    OWN --> DESC["AgentDescriptor.Model<br/>cost"]
    CLIENT --> CAT
    DESC --> CAT
```

For an agent source, the compiler does not run, so two things are yours. Build the
chat client with `IModelProviderRegistry.CreateChatClientAsync` inside
`ResolveAsync`: it applies the tenant's own provider key and egress policy, and it
adds the same model rings a compiled agent gets. Set `AgentDescriptor.Model` in
`ListAsync`: the run record reads the model from the descriptor, and without it the
run has no cost. Do not cache an agent whose client carries a tenant's own key
across tenants; `HasTenantProviderOverrideAsync` tells you when it does.
`samples/Tracon.Samples.MafFeatures` builds such a source, and its tests show the
tenant's key reach the chat client and the cost reach the run record. [Use any
Microsoft Agent Framework feature](/guides/any-maf-feature/) says when to choose this
shape over a factory agent.

```csharp
public async ValueTask<AIAgent?> ResolveAsync(string name, string? culture = null, CancellationToken ct = default)
{
    var definition = await _repository.GetAsync(name, ct);
    return definition is null
        ? null
        : await _compiler.CompileCachedAsync(definition, _cache, _tenants.TenantId, culture, ct);
}
```

## Source contract

Sources are singleton and calls can run concurrently. Keep no request or run state in
fields, and do not capture scoped services. `ListAsync()` is on the run path: it must
be repeatable and side-effect free. Tracon does not add a global snapshot, timeout,
retry, or circuit breaker for a source.

`ListAsync()` and `ResolveAsync()` must describe the same agent set: a name
`ResolveAsync()` resolves has to appear in `ListAsync()`, and vice versa. Return stable
descriptors and never mutate one — or its nested collections — after returning it. The
catalog defensively copies each descriptor at its own boundary too, but that is
belt-and-suspenders, not a license to violate the contract; a source that shares a
mutable list with the catalog is still a bug the copy merely contains.

If the source builds agents from an `AgentDefinition`, copy the fields the host reads off the
descriptor: `SkillNames` and `Skills` for the A2A and MCP exposure check, and `ExtensionNames`
for the catalog. A descriptor that names skills and leaves `Skills` empty reads as "the read
approval is on", and the host refuses to expose the agent even when the definition turned the
approval off. A descriptor without `ExtensionNames` lists an agent compiled with an extension
as one without it.

Honor the `CancellationToken` you are given — Tracon adds no timeout of its own, so
an uncancellable source blocks its caller indefinitely. A genuine
`OperationCanceledException` must propagate; do not catch and convert it into anything
else. "Genuine" means the token you were given is cancelled: an
`OperationCanceledException` while it is not — your own HTTP client timing out surfaces
as `TaskCanceledException` — is a source failure like any other, so listing skips the
source and resolution fails with `TraconAgentSourceException`.

A source can be global or tenant-aware; when it reads `ITenantContext`, any local cache
must include the tenant identity. Startup and background calls see the default tenant.

## Priority and management API

Smaller priority values win a name collision. `AgentSourcePriority.Code` (`0`) and
`AgentSourcePriority.Database` (`100`) are the values Tracon's own built-in sources
use, not values a custom source is barred from choosing. Pick `1` through `99` to run
between code and database definitions, or `101` or greater to run after the database.
Choosing `0` or `100` ties with the matching built-in source instead, and DI
registration order breaks the tie — the same rule as any other equal-priority pair.

Set custom descriptors to `AgentDefinitionOrigin.Custom`. They appear in the console
but are read-only: the management API cannot update or delete a definition that your
source owns.

## Prove the implementation

```csharp
using Tracon.Testing.Contracts.AgentSources;

public sealed class GitAgentSourceTests : AgentSourceContract
{
    protected override string KnownAgentName => "support";
    protected override ValueTask<IAgentSource> CreateSourceAsync()
        => new(new GitAgentSource(/* test repository and services */));
}
```

Derive `VersionedAgentSourceContract` when your source implements
`IVersionedAgentSource`. Derive `TenantAwareAgentSourceContract` when its list changes
by tenant. The suite checks repeatability, concurrency, cancellation, descriptor
integrity, and list/resolve consistency without a network call.

## Scale and operations

`GET /api/agents` is not paged. Resolution also scans the winning source's list, and
version resolution can list each source until it finds the owner. A source holding many
definitions should keep its own correctly invalidated cache. Source failures appear in
logs, diagnostics, and the `tracon.agent_source.failures` metric. List failures are
isolated; resolution failures stop so a lower-priority agent cannot run by mistake.

## Read next

- [Agents](/concepts/agents/) — the catalog and agent-definition model
- [Model providers](/guides/model-providers/) — write a custom model provider
- [Reference: IAgentSource](/api/tracon.iagentsource/) — full API contract
