How to Read Microsoft Agent Framework Skills from a Database Instead of the File System

Most teams start with file-based skills because that is the built-in model in Microsoft Agent Framework today. In .NET, the built-in FileAgentSkillsProvider discovers SKILL.md files from directories, advertises skill names and descriptions in the prompt, returns the full skill body through load_skill, and reads supporting files through read_skill_resource. That model is clean, portable, and easy to reason about. It is also fundamentally file-system oriented. (Microsoft Learn)

That becomes a constraint as soon as skills stop behaving like static project assets. The moment you want tenant-specific skills, runtime enablement, admin editing, approval workflows, audit trails, or controlled rollouts, a database-backed model starts to make more sense. Microsoft’s own recent Python update calls this out directly: not every skill fits a directory on disk, and one of the explicit motivations for code-defined skills is that sometimes the skill content comes from a database. (Microsoft for Developers)

The good news is that you do not need to fight the framework to do this. The right move is not to bend the file provider into something it is not. The right move is to build your own AIContextProvider in .NET that reads skill metadata and content from your database, then exposes the same progressive-disclosure pattern the file provider already uses. Microsoft documents AIContextProvider as the extension point for adding instructions, messages, and tools before each run, and for processing results after the run. (Microsoft Learn)

Why move skills into a database

A database-backed skill model is usually the right choice when skills are operational content rather than developer-owned files. If your operations team needs to update a policy skill without waiting for a code deploy, or if one client should see a different version of a skill than another client, the database becomes the control plane. You also gain versioning, effective dates, staged rollout, audit history, and the ability to disable a bad skill without rebuilding the app. Those are not nice-to-haves in enterprise systems. They are the operating model.

There is also a prompt-efficiency benefit. Agent Framework’s skill design already assumes a progressive-disclosure model: advertise a compact description first, load the full skill only when relevant, then load resources only if needed. That keeps the context window lean while still giving the model access to deeper domain knowledge on demand. A database-backed provider should preserve that exact pattern rather than dumping every skill into the system prompt on every turn. (GitHub)

How Microsoft Agent Framework is structured today

The built-in file provider is useful because it shows the contract you want to preserve. Microsoft documents the current .NET provider as an AIContextProvider that implements three steps: advertise skill names and descriptions, return the full SKILL.md body through load_skill, and read supporting resources on demand. The same docs note that the current .NET package is still at v1.0.0-rc2 and that some details are prerelease, so this is an area to watch as the framework matures. (Microsoft Learn)

For custom behavior, Microsoft documents AIContextProvider as running around each invocation. The provider can contribute instructions, messages, and tools before execution, and can process results afterward. The simplest path is to override ProvideAIContextAsync and StoreAIContextAsync. If you need finer control over how messages and tools are merged, you can override InvokingCoreAsync and InvokedCoreAsync. (Microsoft Learn)

That matters because it gives you the exact hook you need. Your database-backed provider can query the skills table during ProvideAIContextAsync, advertise only the skills relevant to the current tenant or user, and attach function tools for load_skill and read_skill_resource. Microsoft documents function tools in .NET through AIFunctionFactory.Create, which turns ordinary C# methods into agent-callable tools. (Microsoft Learn)

The design I recommend

Do not store raw skill markdown in the system prompt. Store metadata separately from content, and let the agent ask for the full skill only when it needs it. That keeps the token footprint low and gives you a natural place to apply authorization and versioning.

A practical schema looks like this:

  • AgentSkillsSkillIdNameDescriptionInstructionBodyVersionTenantIdIsEnabledEffectiveFromUtcEffectiveToUtc
  • AgentSkillResourcesResourceIdSkillIdResourceNameContentTypeBodyVersion
  • AgentSkillRevisions: immutable audit history for each change
  • AgentSkillRollouts: optional staged rollout targeting by tenant, environment, or percentage

The key is to separate what the model sees first from what the model can load later. Your advertisement layer should stay small and descriptive. Your content layer can be detailed and versioned.

How to implement it in .NET

Here is the shape of a custom provider. It reads visible skills from the database at invocation time, injects a short instruction block listing those skills, and exposes tools that fetch the full skill body and any named resources.

using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
public sealed class DatabaseSkillsProvider : AIContextProvider
{
private readonly ISkillRepository _repo;
public DatabaseSkillsProvider(ISkillRepository repo) : base(null, null)
{
_repo = repo;
}
protected override async ValueTask<AIContext> ProvideAIContextAsync(
InvokingContext context,
CancellationToken cancellationToken = default)
{
var tenantId = ResolveTenant(context);
var skills = await _repo.ListAdvertisedSkillsAsync(tenantId, cancellationToken);
var advertised = string.Join("\n", skills.Select(s => $"""
<skill>
<name>{s.Name}</name>
<description>{s.Description}</description>
</skill>
"""));
return new AIContext
{
Instructions = $"""
You have access to the following skills:
{advertised}
When a task matches a skill, call load_skill with the exact skill name.
Call read_skill_resource only when you need an additional file or reference.
""",
Tools =
[
AIFunctionFactory.Create(LoadSkillAsync),
AIFunctionFactory.Create(ReadSkillResourceAsync)
]
};
}
[Description("Load the full instructions for a skill.")]
public async Task<string> LoadSkillAsync(
[Description("Exact skill name")] string skillName,
CancellationToken cancellationToken = default)
{
var skill = await _repo.GetCurrentSkillAsync(skillName, cancellationToken);
return skill?.InstructionBody ?? $"Skill '{skillName}' was not found.";
}
[Description("Read a named resource for a skill.")]
public async Task<string> ReadSkillResourceAsync(
[Description("Exact skill name")] string skillName,
[Description("Resource name")] string resourceName,
CancellationToken cancellationToken = default)
{
var resource = await _repo.GetCurrentResourceAsync(skillName, resourceName, cancellationToken);
return resource?.Body ?? $"Resource '{resourceName}' was not found for skill '{skillName}'.";
}
private static string ResolveTenant(InvokingContext context) => "default";
}

And you wire it into the agent exactly the same way you would wire in any other context provider:

AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
Name = "SkillsAgent",
ChatOptions = new()
{
Instructions = "You are a helpful assistant."
},
AIContextProviders =
[
new DatabaseSkillsProvider(skillRepository)
]
});

This design stays aligned with the framework instead of inventing a parallel mechanism. The agent still sees skills as advertised capabilities, still calls tools to load them, and still keeps the context window lean. That is important because it lets you move storage without changing the agent’s interaction model. The framework’s documented file-based provider uses exactly that progressive-disclosure shape, and .NET function tools are intended to be added in precisely this way. (Microsoft Learn)

The part people miss: update visibility

This is where a database-backed design gets subtle.

Microsoft documents that AIContextProvider is invoked at the start of each run to provide additional context. That means if your provider queries the database inside ProvideAIContextAsync, any metadata changes you made before that run can be reflected on that run. Likewise, if your load_skill and read_skill_resource tools read the database directly and you do not add your own cache, the content fetched by those tools can reflect the latest committed version at the moment the tool is called. That is the cleanest and most predictable model. (Microsoft Learn)

In practical terms, that means:

  • A changed skill description becomes available on the next agent invocation, because skill advertisement happens before each run.
  • A changed skill body becomes available the next time the agent calls load_skill.
  • A changed resource becomes available the next time the agent calls read_skill_resource.

That timing is an implementation consequence of the documented lifecycle, not a special built-in database feature. If you introduce caches, precomputed prompt fragments, or session-persisted skill snapshots, you change those semantics.

This is where many teams get burned. Microsoft also documents that the same AIContextProvider instance is shared across sessions, and that session-specific state should live in AgentSession, not in provider instance fields. It also warns that sessions are agent- and provider-specific, and reusing a session with a different agent configuration or provider can lead to invalid context. The implication is important: if your session history or session state still contains older skill content, that old content can continue influencing the model even after the database row has changed. (Microsoft Learn)

So the right answer to “when is an updated skill accessible?” is:

Immediately for new reads, unless you cache. Potentially not immediately for ongoing sessions, if older skill content is still present in history or session state.

That is why production systems should version skills explicitly and decide what happens to in-flight sessions when a skill changes.

The update model I recommend

Treat skill changes like configuration deployment, not like editing a note in a CMS.

The safest pattern is this:

  1. Write a new immutable revision instead of updating the current row in place.
  2. Mark that revision with an effective timestamp or rollout flag.
  3. Keep a pointer to the “current” revision for new runs.
  4. Decide whether existing sessions should continue on the old revision or be upgraded.
  5. Include the revision identifier in any tool result so you can audit what the agent actually used.

If the change is minor, such as fixing wording or adding an example, letting new runs pick up the newest revision is usually fine. If the change is breaking, such as changing policy logic or action constraints, create a new skill version and either start a new session or mark the old session as bound to the earlier version.

A practical repository pattern looks like this:

public interface ISkillRepository
{
Task<IReadOnlyList<AdvertisedSkill>> ListAdvertisedSkillsAsync(
string tenantId,
CancellationToken ct);
Task<SkillRevision?> GetCurrentSkillAsync(
string skillName,
CancellationToken ct);
Task<SkillResourceRevision?> GetCurrentResourceAsync(
string skillName,
string resourceName,
CancellationToken ct);
Task<SkillRevision?> GetSkillRevisionAsync(
string skillName,
string version,
CancellationToken ct);
}

If you want deterministic behavior within a session, store the selected skill version in AgentSession the first time the skill is loaded, then keep serving that version for the life of the session. Microsoft’s session-state guidance exists exactly for this sort of per-session binding. (Microsoft Learn)

The risks you should plan for

The first risk is unauthorized skill exposure. In a file-based model, your boundary is usually the deployment package. In a database-backed model, the risk surface grows because your provider is now responsible for deciding what skills the model even knows exist. Filter before advertisement, not only at load time. If the model should not know a skill exists, do not list it in the injected instructions. The built-in provider’s security guidance is blunt: only use skills from trusted sources. (Microsoft Learn)

The second risk is stale or inconsistent behavior during rollout. If the advertisement query sees version 5 but load_skill reads version 6 because a deployment happened between those two steps, the model can end up operating against a moving target. You can solve this by advertising skill name plus version, or by issuing a per-run snapshot token and having load_skill resolve against that snapshot.

The third risk is prompt injection and unsafe actions. A database makes it easier to edit skill content, which also makes it easier to introduce bad instructions. Review and governance matter more, not less. In Python, Microsoft now supports skill scripts and explicitly recommends sandboxing, approvals for sensitive operations, and audit logging. In .NET, script execution for skills is not yet supported, but the approval pattern for function tools is already documented. If a skill can trigger a side effect such as sending an email, changing a record, or calling an external API, wrap the tool with approval before execution. (Microsoft for Developers)

The fourth risk is operational ambiguity. When an answer goes wrong, you need to know which skill revision, which resource revision, and which tool outputs were used. Without that, debugging becomes guesswork. The moment skills become runtime data, observability becomes part of the design, not an afterthought.

What I would do in production

I would use the database for skill metadata, instruction bodies, revisions, rollout rules, and audit history. I would keep the runtime contract identical to the built-in provider: advertise, load, then read resources. I would make new runs read fresh data by default, but bind a skill version inside the session once a skill is first loaded if consistency matters more than instant propagation.

I would also add four operational controls:

  • approval workflow for publishing a skill revision
  • explicit effective_from support
  • immutable audit trail of every revision
  • runtime logs that record skill name, revision, and resource revisions used per run

That gives you the flexibility of a database without turning skill behavior into a moving target.

Finally

Reading skills from a database is not a workaround. It is the right architecture when skills become operational assets instead of code assets. Microsoft Agent Framework already gives you the extension point to do it cleanly through AIContextProvider, and the built-in file provider gives you the pattern to preserve. The real engineering work is not the database query. The real work is deciding your update semantics, your versioning model, and your control plane. (Microsoft Learn)

If you get those right, database-backed skills become a major advantage. If you do not, you end up with invisible prompt drift and sessions that behave differently for reasons nobody can explain.