e-naxosMAF COURSE / COURS
Français

MICROSOFT AGENT FRAMEWORK · 0.16.0-rc.1

Controlled facts and context drift

Admit, correct and forget assertions without recycling model answers.

Objectives

A correction must change state

A conversation mentions Reporting and PostgreSQL, then corrects them to Billing and SQL Server 2022. Keeping both versions in history leaves priority to the model. EP013 shows this drift: a plausible answer can revive a discarded hypothesis or lose an earlier prohibition. Controlled memory makes correction explicit: a key is replaced and its provenance remains visible.

A deliberately narrow contract

Only key=value declarations, separated by semicolons or newlines, are admitted. Allowed keys are product, module, priority, database, constraint, customer, version, environment, symptom and log. The writer proposes typed candidates without a session, from the current User message only. The host checks all keys and values against explicit declarations and requires an exact quotation. A question or assistant answer is never automatically promoted into a fact.

var declarations = ExplicitAssertions.Parse(userText);
if (response.Result.Facts is null || response.Result.Facts.Count != declarations.Count
    || response.Result.Facts.Any(f => f is null || !declarations.TryGetValue(f.Key, out var value) || value != f.Value))
    throw new InvalidDataException("Extraction does not match the explicit key=value assertions. No semantic guess is admitted.");
memory.Apply(turn, response.Result.Facts, DateTimeOffset.UtcNow);

shared/Maf.Memory/MemoryExtraction.cs · L25–L29 · 9a895c94534c

Source code · Maf.Memory · Folder

Admission is atomic: an unknown key, duplicate candidate, missing evidence or wrong semantic association rejects the entire batch. The final declaration of a repeated key in the message is authoritative. A quotation establishes that the user wrote those words; it does not establish the assertion as true. If extraction fails, previous facts remain and the diagnostic reports no fallback. Inspect mem before continuing after a rejected correction.

Provenance, bounds and stale updates

Each fact carries Key, Value, Evidence, Turn and UTC RecordedAt. The store and each extraction are limited to five facts, values to 256 characters, evidence to 512 and the message to 4096. A new key evicts the fact with the oldest turn; ordinal key order breaks ties. A correction replaces the same key. An Epoch/Number ticket prevents an older extraction or one predating forget from reintroducing data.

public void Apply(MemoryTurn turn, IReadOnlyList<FactCandidate>? candidates, DateTimeOffset recordedAt)
{
    lock (_gate)
    {
        if (turn.Epoch != _epoch || turn.Number != _turn)
            throw new InvalidOperationException("Memory update is stale or belongs to a forgotten conversation.");
        if (candidates is null || candidates.Count > 5)
            throw new ArgumentException("Extraction must contain at most five facts.");
        var keys = new HashSet<string>(StringComparer.Ordinal);
        foreach (var fact in candidates)
        {
            if (fact is null || !AllowedKeys.Contains(fact.Key ?? "") || !keys.Add(fact.Key!)
                || string.IsNullOrWhiteSpace(fact.Value) || fact.Value.Length > 256
                || string.IsNullOrWhiteSpace(fact.Evidence) || fact.Evidence.Length > 512
                || !turn.UserText.Contains(fact.Evidence, StringComparison.Ordinal)
                || !fact.Evidence.Contains(fact.Value, StringComparison.Ordinal))
                throw new ArgumentException("Invalid fact: use a unique allowed key and a value quoted in current user evidence.");
        }

        foreach (var fact in candidates)
            _facts[fact.Key] = new(fact.Key, fact.Value, fact.Evidence, turn.Number, recordedAt);

        while (_facts.Count > 5)
        {
            var oldest = _facts.Values.OrderBy(f => f.Turn).ThenBy(f => f.Key, StringComparer.Ordinal).First();
            _facts.Remove(oldest.Key);

shared/Maf.Memory/WorkingMemory.cs · L39–L64 · 9a895c94534c

Source code · Maf.Memory · Folder

Manual injection or native provider

Fact lifecycle; the store stays in-process
  1. User message → business answer
  2. Success → extract external User only → host validation
  3. WorkingMemory → injection into the next call of either agent
  4. forget [key] → remove selected facts + replace both sessions

Demo150 adds ToPromptBlock to the message before RunAsync, then extracts after the answer. Demo160 supplies the same block through AIContextProvider before invocation and, after success, extracts User messages whose source is External. This excludes generated answers and its own injected context. The new assertion is already in the current message; corrected store data serves later calls. Two distinct business sessions share this store without merging their histories.

protected override async ValueTask StoreAIContextAsync(InvokedContext context,
    CancellationToken cancellationToken = default)
{
    if (context.InvokeException is not null)
        return;
    foreach (var message in context.RequestMessages.Where(m => m.Role == Microsoft.Extensions.AI.ChatRole.User
        && m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External))
    {
        if (!string.IsNullOrWhiteSpace(message.Text))
            report(await extraction.UpdateAsync(message.Text, cancellationToken));

shared/Maf.Memory/MemoryExtraction.cs · L79–L88 · 9a895c94534c

Source code · Maf.Memory · Folder

Compare policies without declaring a winner

Demo170 runs a fixed chronology, replacing the old video menu: full history, last three turns, unverified generated summary, bounded facts and native MAF request compaction. An empty summary or one longer than 700 characters stops comparison without replacement text. A window can lose an earlier prohibition; a summary can distort it; five facts can evict still-useful information. Provenance can make facts larger than a very short dialogue.

CompactionProvider reduces the view sent to the model while retaining serializable source history. AsChatReducer reduces retained history after additions: the snapshot then contains only the reduced list. These are different effects. A characters/4 estimate measures neither billed tokens nor total cost; include writer and summarizer calls and retain unknown when the provider does not report usage.

    public static IChatClient CreateRequestView(IChatClient chat)
    {
#pragma warning disable MAAI001
        return chat.AsBuilder().UseAIContextProviders(new CompactionProvider(
            new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(3), minimumPreservedTurns: 3))).Build();
#pragma warning restore MAAI001
    }

    /// <summary>Reduces retained history after each completed exchange; serialization saves the reduced list.</summary>
    public static InMemoryChatHistoryProvider CreateBoundedHistory()
    {
#pragma warning disable MAAI001
        return new(new InMemoryChatHistoryProviderOptions
        {
            ChatReducer = new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(3), minimumPreservedTurns: 3).AsChatReducer(),
            ReducerTriggerEvent = InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.AfterMessageAdded

shared/Maf.Memory/ContextExperiment.cs · L11–L26 · 9a895c94534c

Source code · Maf.Memory · Folder

Correct then forget

In Demo150 then Demo160, enter module=Reporting; database=PostgreSQL, mem, then module=Billing; database=SQL Server 2022 and mem. Check the two corrected keys and their quotations. Ask Where can I find logs? to use the other agent; inspect mem after that call. forget module must retain database but replace both histories. forget removes every fact; a later question must not recreate them by inference. Facts are not persisted and disappear on exit.

dotnet test tests/Maf.Memory.Tests/Maf.Memory.Tests.csproj -m:1 -c Release --no-build --no-restore

Memory does not authorize an action

These user assertions remain unverified data labeled as such. They replace neither business reference data, authentication nor tool authorization. Forgetting covers the active store and both agent sessions; it does not purge external backups. A larger application must define ownership, expiry, revocation and fact review. Live variants require chat capable of respecting contracts; scripted tests qualify no model.

Demos and document context

Prerequisites

Continue reading

Previous

Next

lot008-memory-rag-1 · 2026-10-24 · 9a895c94534cd5fc6f3da883e37e5f7856e588c1