# Telling an agent what day it is

> A model can't see a clock, so add today's date to every run with a context provider that a test can control.

Published: 23 Oct 2026
Series: Microsoft Agent Framework in C#, part 6
URL: https://kootstra.io/writing/ai-agent-current-date-csharp/

**The idea:** the model only knows its training data and the request in front of it. Today's date has to be in that request, on every run.

Ask a kitchen assistant to plan dinners for "the rest of this week" and it needs to know which day it is. The model cannot look at a clock. It knows what it learned in training and what the current request contains, and the date is in neither. So it has to guess, or ask. The fix is to put the date into every request, and there are three places to do it. (A fourth is a function tool that returns the date, but that costs an extra round trip, and the model has to decide to call it.) The one that holds up in a long-running service is a context provider: a small class that the agent asks for extra context at the start of each run. Here it adds one line, "Today is Thursday 22 October 2026.", and reads the date from a clock that a test can replace.

**Versions:** Microsoft Agent Framework 1.24.0, .NET 10.

**Code:** [samples/agent-framework/06-current-date](https://github.com/nkootstra/samples/tree/main/agent-framework/06-current-date), a runnable version of this post with its tests.

**Who this is for:** C# developers who have kept a conversation in a session, as in [Remembering the conversation with agent sessions](https://kootstra.io/writing/agent-framework-sessions-csharp/).

> [!NOTE]
> The code runs on Claude Haiku 5.5, but it depends only on `IChatClient`: [the first post](https://kootstra.io/writing/microsoft-agent-framework-first-csharp-agent/#swap-the-provider-by-changing-one-statement) shows the swap to OpenAI or Ollama.

## Set up a chat client for Claude Haiku

A console app with the `Microsoft.Agents.AI` and `Microsoft.Agents.AI.Anthropic` packages. The chat client talks to Claude Haiku 5.5 and reads its key from `ANTHROPIC_API_KEY`. The instructions and the question are shared by every step below:

```csharp
using Anthropic;
using Anthropic.Core;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
    ?? throw new InvalidOperationException("Set ANTHROPIC_API_KEY.");

IChatClient chatClient =
    new AnthropicClient(new ClientOptions { ApiKey = apiKey })
        .AsIChatClient("claude-haiku-5-5", 1024);

const string Instructions =
    "You help one household decide what to cook. "
    + "Keep answers short.";
const string Question =
    "Plan dinners for the rest of this week, one line per day.";
```

## Without a date, the model has to guess

The first agent has instructions and nothing else:

```csharp
AIAgent agent = chatClient.AsAIAgent(
    name: "kitchen-assistant",
    instructions: Instructions);

Console.WriteLine(await agent.RunAsync(Question));
```

This run was on a Thursday:

```text
I don't know which day it is, what's in your fridge, or what your household likes, so here's a generic plan you can adjust:

- **Monday:** Sheet-pan chicken thighs with roasted potatoes and green beans
- **Tuesday:** Turkey tacos with black beans and salsa
- **Wednesday:** Pasta primavera with garlic, olive oil, and parmesan
- **Thursday:** Lentil soup with crusty bread
- **Friday:** Homemade pizza night with whatever toppings you have
- **Saturday:** Stir-fry with rice and frozen mixed vegetables
- **Sunday:** Slow-cooker beef stew

Tell me which days are left, your dietary needs, and what ingredients you have, and I'll tailor it.
```

In this run the model did not invent a date. It planned a full week from Monday and asked which days are left. That is a reasonable answer to a question it cannot answer, but it is not what the person asked for. "The rest of this week" means nothing without today's date, and so do "tomorrow" and "this weekend".

## Two quick ways to add the date, and their costs

The obvious fixes put the date into text the agent already sends. Both work for a single run, and both compile:

```csharp
using System.Globalization;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

public static class Alternatives
{
    public static AIAgent DateInInstructions(
        IChatClient chatClient, string instructions)
    {
        string today = DateTime.Now.ToString(
            "dddd d MMMM yyyy", CultureInfo.InvariantCulture);

        AIAgent agent = chatClient.AsAIAgent(
            name: "kitchen-assistant",
            instructions: $"{instructions} Today is {today}."); // [!code highlight]
        return agent;
    }

    public static async Task<AgentResponse> DateInMessage(
        AIAgent agent, AgentSession session, string question)
    {
        string today = DateTime.Now.ToString(
            "dddd d MMMM yyyy", CultureInfo.InvariantCulture);

        return await agent.RunAsync(
            $"Today is {today}. {question}", session); // [!code highlight]
    }
}
```

`DateInInstructions` reads the date once, when the agent is created. The instructions are a fixed string from then on. That is fine in a console app that starts, answers and exits. In a web service the agent is usually created once at startup and kept, so on Thursday it still tells the model that it is Wednesday.

`DateInMessage` reads the date on every run, so the date is always current. The cost is where the date ends up. The app's fact ("today is...") is now mixed into the text the person typed. With a session, the history keeps every request message and sends it again on the next turn. So the stored history fills up with old dates. A conversation resumed next week still carries last week's date in its earlier messages, next to the new one.

The format string uses `CultureInfo.InvariantCulture` in both methods. Without it, a server with a Dutch culture writes "donderdag 22 oktober 2026", and the date text changes with the machine the app runs on.

## A context provider adds the date on every run

A context provider is a class that the agent calls at the start of every run. It can add instructions, messages or tools to the run. Instructions and tools apply to that one run; messages it adds can also be stored in the history, as shown below. In Agent Framework it derives from `AIContextProvider` and overrides `ProvideAIContextAsync`, which returns an `AIContext` with the extra context:

```csharp
using System.Globalization;
using Microsoft.Agents.AI;

public sealed class TodayContextProvider(TimeProvider clock)
    : AIContextProvider
{
    protected override ValueTask<AIContext> ProvideAIContextAsync(
        InvokingContext context,
        CancellationToken cancellationToken = default)
    {
        string today = clock.GetLocalNow().ToString( // [!code highlight]
            "dddd d MMMM yyyy", CultureInfo.InvariantCulture);

        return ValueTask.FromResult(
            new AIContext { Instructions = $"Today is {today}." });
    }
}
```

The date comes from a `TimeProvider`, the .NET abstraction for the clock, not from `DateTime.Now`. The app passes `TimeProvider.System`, which reads the real clock. A test passes its own `TimeProvider` with a fixed time, and the provider writes that date instead.

`AIContext` has three properties: `Instructions`, `Messages` and `Tools`. The provider fills only `Instructions`. The agent adds that text to its own instructions, joined with a line break. The instructions for this run read:

```text
You help one household decide what to cook. Keep answers short.
Today is Thursday 22 October 2026.
```

The provider holds no per-conversation state. The same instance serves every session of the agent, so anything that differs per conversation belongs in the session, not in a field of the provider. `ProviderSessionState<TState>` is the helper for that: it reads and writes a provider's state in the session's `StateBag` under a key of its own.

## Register the provider in the agent options

The `AsAIAgent` overload with `name` and `instructions` has no parameter for context providers. The overload that takes `ChatClientAgentOptions` has one. The agent's instructions move into `ChatOptions.Instructions`, and the provider goes into `AIContextProviders`:

```csharp
agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
    Name = "kitchen-assistant",
    ChatOptions = new ChatOptions { Instructions = Instructions },
    AIContextProviders = // [!code ++]
        [new TodayContextProvider(TimeProvider.System)], // [!code ++]
});

AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync(Question, session));
```

Same question, same day:

```text
**Thursday:** Sheet-pan sausages with peppers and potatoes.
**Friday:** Homemade pizza with whatever toppings you have on hand.
**Saturday:** Slow-cooker chili, which will also cover Sunday's lunch.
**Sunday:** Roast chicken with carrots and gravy.
```

In this run the plan starts on Thursday and stops on Sunday, without a question back. `ChatClientAgent` calls its context providers on every run. The one exception is a run that continues a response which is still in progress. A long-lived agent therefore reads the clock again for each request, and Friday's requests say Friday.

## The date stays out of the session history

The session from the last run holds the conversation so far. Printing its history shows what was stored:

```csharp
if (session.TryGetInMemoryChatHistory(out List<ChatMessage>? history))
{
    foreach (ChatMessage message in history)
    {
        Console.WriteLine($"{message.Role}: {message.Text}");
    }
}
```

```text
user: Plan dinners for the rest of this week, one line per day.
assistant: **Thursday:** Sheet-pan sausages with peppers and potatoes.
[...]
```

The user's question and the answer are there. "Today is Thursday 22 October 2026." is not. The instructions a provider returns apply only to the current run. The agent passes them to the chat client as instructions, not as a message, so the history provider never sees them.

That is a property of `Instructions`, not of context providers in general. Messages a provider returns in `AIContext.Messages` are added to the request messages. The default history provider stores request messages unless they came from the history itself. So a date sent as a message would end up in the stored history after all, the same problem as `DateInMessage`. For a fact that is true only today, `Instructions` is the right property.

The framework does not check what a provider returns. It accepts the output as it is and merges it into the request. A provider that only formats the clock is safe. A provider that reads text from a database or another service passes that text straight to the model, so it needs the same care as any other untrusted input.

## The date is the server's date

`GetLocalNow` converts the clock to the provider's `LocalTimeZone`, which for `TimeProvider.System` is the local time zone of the machine the app runs on. For a console app on your own laptop, that is your day. For a service hosted in UTC, it is the day in UTC. A household in Amsterdam that asks for "tomorrow" shortly after midnight local time still gets the previous day as "today", because in UTC it is still the evening before. The provider gives the model the date of the server, and the household's date is a different value whenever the two time zones fall on different days. The fix is to carry the household's time zone with the conversation, in the session through `ProviderSessionState<TState>` or per run in `AgentRunOptions.AdditionalProperties` (which a provider can read through `AIAgent.CurrentRunContext`), and to convert the clock to that zone before formatting the date.

## Try it

- Write a small `TimeProvider` subclass that overrides `GetUtcNow` to return Saturday 17 October 2026 at 12:00 UTC and `LocalTimeZone` to return `TimeZoneInfo.Utc`, so the date is Saturday wherever the test runs (or use a `FakeTimeProvider` from the `Microsoft.Extensions.TimeProvider.Testing` package, as the sample's tests do; its time zone is UTC unless you call `SetLocalTimeZone`), pass it to `TodayContextProvider` instead of `TimeProvider.System`, and ask the same question. Check that the plan now covers only the weekend.
- Add a second line to the provider's `Instructions`, for example "The household does not eat meat.", and ask again. The rule now reaches every run without changing the agent's instructions or the stored history.
