MICROSOFT AGENT FRAMEWORK · 0.16.0-rc.1
Actions and in-process approval
Authorize an exact proposal and verify the actual SQLite write.
Objectives
- SQLite backlog and readback
- In-process approval and refusal
- Pipeline and durable-decision boundary
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.
- Catalogue → Analysis → BacklogDraft
- Host validation → Fixed proposal
- Host permission + exact approval
- 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;
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);
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."); }
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-buildWith 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
- Demo070 — SQLite Backlog
- Demo080 — Approved Backlog
- Demo090 — Complete Pipeline
- Demo100 — Controlled Action
Previous
Next
