e-naxosMAF COURSE / COURS
Français

MICROSOFT AGENT FRAMEWORK · 0.16.0-rc.1

Actions and in-process approval

Authorize an exact proposal and verify the actual SQLite write.

Objectives

The path of an action

Demo090 explicitly assembles in C#: trusted catalogue, analysis without sensitive tools, deserialization, draft validation, a fixed proposal, approval request, invocation and readback. The writer agent receives one create_backlog_item tool and a proposalId. It receives no parameters for replacing Title or Summary after review. This pipeline is not yet a MAF Workflows graph.

Pipeline: host-controlled sequence
  1. Catalogue → Analysis → BacklogDraft
  2. Host validation → Fixed proposal
  3. Host permission + exact approval
  4. ToolOutcome → SQLite readback

A real database, evidence to read back

BacklogStore uses Microsoft.Data.Sqlite and SQL parameters. Insert validates the draft again immediately before writing and returns the SQLite identifier. List reads the twenty latest rows in descending identifier order. The default database is %LOCALAPPDATA%/MafBaseTechnique/lot005-backlog.db; MAF_DEMO_DB_PATH selects an isolated working path. Startup performs no reset.

public IReadOnlyList<BacklogItem> List()
{
    using var connection = Open();
    using var command = connection.CreateCommand();
    command.CommandText = "SELECT Id, Title, Kind, Priority, Component, Summary FROM BacklogItems ORDER BY Id DESC LIMIT 20";
    using var reader = command.ExecuteReader();
    var items = new List<BacklogItem>();
    while (reader.Read()) items.Add(new(reader.GetInt64(0), reader.GetString(1), reader.GetString(2),
        reader.GetString(3), reader.GetString(4), reader.GetString(5)));
    return items;

shared/Maf.Backlog/BacklogStore.cs · L45–L54 · 9a895c94534c

Source code · Maf.Backlog · Folder

A refused action can still leave a file and table created during reading: expected evidence is no new business row, not no file. With an existing database, compare before and after. Do not delete an existing database to make a demonstration convincing; choose a new test path.

Manual, then native approval

Demo070 asks for confirmation inside the function body. Demo080 wraps the function in ApprovalRequiredAIFunction: MAF returns ToolApprovalRequestContent before the effect. The host registers the request and arguments; request.CreateResponse(bool) creates the decision, sent back in a User ChatMessage with the same AgentSession. Only exact input y approves; empty input and every other answer refuse.

ApprovalReview binds session, name, call identifier and arguments. Foreign objects, changed arguments, another session and consumed requests are rejected. Multiple requests are reviewed separately. The demo loop is limited to four rounds; a still-pending request is never success. Without an approval response, no authorized continuation is sent.

var function = AIFunctionFactory.Create(gate.Create, "create_backlog_item");
var writer = chat.AsAIAgent(name: "BacklogWriter", instructions:
    "Call create_backlog_item exactly once with the host proposal ID. Do not invent success. Report the tool outcome in English.",
    tools: [new ApprovalRequiredAIFunction(function)]);
var session = await writer.CreateSessionAsync(stop.Token);
var review = new ApprovalReview(session, gate);

demos/Demo080_ApprovedBacklog/Program.cs · L44–L49 · 9a895c94534c

Source code · Demo080 · Folder

The guard stays in the host

WriteGate also copies the mutable question array before review. It refuses an invalid contract, false canWrite, missing approval, mismatched identifier or consumed capability. MAF_DEMO_READ_ONLY=1 removes permission: even y cannot restore it. Once authorized, invocation consumes the grant before insertion; storage failure does not cause a blind retry.

if (draft.Validate().Length != 0) result = new("Invalid", null, "Business contract failed.");
else if (!canWrite || !approved || consumed || proposalId != this.proposalId)
    result = new("Refused", null, "Permission, approval or unused matching proposal is missing.");
else
{
    consumed = true;
    approved = false;
    try { result = new("Created", store.Insert(draft), "SQLite write observed by the host."); }

shared/Maf.Backlog/WriteGate.cs · L50–L57 · 9a895c94534c

Source code · Maf.Backlog · Folder

Approval and capability live in memory; SQLite retains the row, not that decision. Restarting or creating another proposal does not durably deduplicate the same intent. Persisted requests and idempotent replay belong to the durability path. The read-only switch illustrates host policy without implementing production application authentication.

Complete procedure: refuse, then approve

$env:MAF_DEMO_DB_PATH = Join-Path $env:TEMP ('maf-foundations-' + [guid]::NewGuid().ToString('N') + '.db')
$env:MAF_DEMO_READ_ONLY = '0'
dotnet run --project demos/Demo090_CompletePipeline/Demo090.App.csproj -c Release --no-build

With chat qualified for structure and tools, enter list to record initial state. Submit: “Login fails for every known account since release 2.1. Reproduce with an existing account. Priority high, component Auth.” Read the draft; if questions remain, clarify and retry. At approval, enter n. Check host observations, then list: no new row. Submit again, inspect the new draft and enter y only if appropriate. Require Created and ItemId, then check that list contains exactly the reviewed fields.

Exit with an empty line. Restart with MAF_DEMO_READ_ONLY=1 and submit another proposal: y must not create a row. Then remove this terminal’s overrides with Remove-Item Env:MAF_DEMO_READ_ONLY and Remove-Item Env:MAF_DEMO_DB_PATH. No call, invalid structure or a pending request remains an explicit outcome; this guided procedure does not claim a new live model run for the site.

Demo pages and exercise

Prerequisites

Continue reading

Previous

Next

lot007-foundations-1 · 2026-10-24 · 9a895c94534cd5fc6f3da883e37e5f7856e588c1