As you may have heard, loops are the hottest thing in AI. The buzz is that rather than you writing prompts, you let your agent do so. The problem is that while all the videos I’ve seen are great at touting the advantages and even explaining the moving parts (though each one has slightly different architecture), I’ve yet to see one that gives concrete C# examples—let alone using the Microsoft Agent Framework.

Soooo, this is the very first in a series. I’m finding my way, stumbling in the dark, so some of this may be a bit simplistic—but I believe it is correct as far as it goes. Remember, however, that the first draft of this post was written by AI. I’ve edited it, but your mileage may vary.
Here’s a thought experiment. Imagine having an agent that keeps trying fixes until a web service stays healthy—retrying configuration changes, re-checking status, and stopping only when a health check returns OK or a safety limit is reached. That behavior—automatic, bounded repetition with an evaluation step between attempts—is exactly what loop programming enables in modern agentic AI. This post explains the pattern, why it matters, and how to implement it in C# using the Microsoft Agent Framework (MAF) concepts.
One-sentence takeaway
Loop programming = agent acts → observes → evaluates → repeats (until accepted or bound). Use MAF’s loop primitives (loop wrapper or middleware + evaluators) to implement this pattern safely in .NET.
Why loop programming matters
- Iterative refinement: reduce hallucinations by asking the model to rework outputs until they meet quality criteria.
- Multi-step tasks: break complex tasks into incremental attempts (repair, check, retry).
- Automation & robustness: automatically retry flaky tools with idempotency and backoff, or orchestrate multi-tool workflows until success.
- Cost/latency tradeoffs: each iteration consumes time and tokens—a deliberate loop strategy balances accuracy and cost.
Core MAF loop terms
- Base agent / client: This is the model wrapped as an agent (an abstraction that receives requests and produces responses).
- Loop wrapper or middleware: A piece that re-invokes the base agent automatically, applying evaluator logic between iterations.
- Evaluator: A component that inspects the previous response and returns Continue or Stop. Examples:
- Marker-based evaluator: stop when the output contains a specific marker (e.g., “DONE”).
- Judge evaluator: ask an auxiliary judge model to say whether the output is acceptable.
- Custom evaluator: your own logic that checks outputs, tool results, or external state.
- Bounds: MaxIterations, timeouts, and cancellation tokens—mandatory to avoid runaway loops.
- AgentRequest: the input to an agent (prompt, tools, options).
- AgentResponse: the output (text, structured data, tool results).
Minimal, runnable-style C# example
Below is a full Program.cs and a simple csproj. The example uses a completion-marker evaluator (“DONE”) and a MaxIterations bound. It is a bit brain-dead, but it will give you a sense of how to string one of these together.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Evaluators;
using Microsoft.Agents.AI.OpenAI.Clients;
class Program
{
static async Task Main(string[] args)
{
// Read API key (set as env var)
string? apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
if (string.IsNullOrEmpty(apiKey))
{
Console.WriteLine("Set OPENAI_API_KEY environment variable and re-run.");
return;
}// 1) Create provider client (provider-specific)}
var openAiClient = new OpenAiProviderClient(new OpenAiOptions { ApiKey = apiKey });
// 2) Convert provider client to an agent abstraction
AIAgent baseAgent = openAiClient.AsAIAgent();
// 3) Create a simple marker evaluator that stops when "DONE" appears
var evaluator = new CompletionMarkerLoopEvaluator("DONE");
// 4) Wrap the base agent with loop behavior and bounds
var loopOptions = new LoopAgentOptions { MaxIterations = 5 };
AIAgent loopAgent = new LoopAgent(baseAgent, evaluator, loopOptions);
// 5) Prepare request
var request = new AgentRequest
{
Prompt = "Summarize this paragraph and keep refining until you append DONE:\n\n" +
"The quick brown fox jumps over the lazy dog. This sentence includes all the letters of the English alphabet."
};
// 6) Use a timeout for safety
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
AgentResponse response = await loopAgent.InvokeAsync(request, cts.Token);
Console.WriteLine("Final response:\n" + response.Text);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation timed out or was canceled.");
}
}
How to adapt this to the real MAF packages
- Install packages
- dotnet add package Microsoft.Agents.AI
- dotnet add package Microsoft.Agents.AI.OpenAI
- The logical steps are always pretty much the same: build a base agent, choose an evaluator, apply loop wrapper or middleware, and call it.
Designing better evaluators (AI judge and custom evaluator)
Marker evaluator (simple)
- Pros: deterministic and cheap.
- Cons: brittle.
AIJudge evaluator (higher quality)
- Pattern: after each iteration, call a small judge prompt that returns YES/NO and a short reason. Use a very explicit evaluation prompt to reduce ambiguity.
- Example judge prompt (send as a separate small completion request):
“You are a strict grader. Given the task: [task description]. Given the model response: [response]. Is the response acceptable? Answer EXACTLY YES or NO followed by a short reason.”
Custom evaluator pattern (checks content + side effects)
- Inspect response text for task markers (like DONE) and for required structured outputs.
- Check external state (database, API) to determine if the side effect succeeded.
- Optionally consult a judge model only when heuristic checks are inconclusive.
Sample custom evaluator
public class KeywordOrJudgeEvaluator : ILoopEvaluator
{
private readonly string _stopKeyword;
private readonly AIAgent? _judgeAgent;
public KeywordOrJudgeEvaluator(string stopKeyword, AIAgent? judgeAgent = null)
{
_stopKeyword = stopKeyword;
_judgeAgent = judgeAgent;
}
public async Task<LoopDecision> EvaluateAsync(AgentResponse lastResponse, CancellationToken ct)
{
if (lastResponse.Text?.Contains(_stopKeyword, StringComparison.OrdinalIgnoreCase) == true)
return LoopDecision.Stop();
if (_judgeAgent != null)
{
var judgeRequest = new AgentRequest
{
Prompt = $"Is the following response acceptable? Answer YES or NO.\n\nResponse:\n{lastResponse.Text}\n\nCriteria: [list your criteria]"
};
var judgeResponse = await _judgeAgent.InvokeAsync(judgeRequest, ct);
if (judgeResponse.Text.Trim().StartsWith("YES", StringComparison.OrdinalIgnoreCase))
return LoopDecision.Stop();
}
return LoopDecision.Continue();
}
}
Practical safety and production best practices
- Always set MaxIterations. Decide MaxIterations based on expected task complexity (2–5 for refinement tasks, more for longer repair flows).
- Use a wall-clock timeout (CancellationTokenSource) to cap total runtime.
- Log each iteration: input prompt, model output, evaluator decision, iteration duration, and any tool calls.
- Be idempotent for side-effecting tool calls: include an idempotency key or track completed steps to avoid duplicate side effects.
- Use backoff and limited retries for transient tool failures.
- Consider human-in-the-loop for high-risk operations.
- Capture metrics: iterations-per-task, average cost per task, success rate after N iterations.
Exponential backoff snippet
int attempt = 0;
while (attempt < maxRetries)
{
attempt++;
try { callTool(); break; }
catch (TransientException)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
}
Cost and latency tradeoffs
- Each loop iteration = another model call (cost + latency). If you expect 2–3 iterations on average, estimate cost = baseCost × expectedIterations + judgeCost (if used).
- Use marker evaluators when iterations are cheap and you can control the prompt reliably; use judge evaluators when marker brittleness is likely.
- For low-latency needs, constrain MaxIterations or move part of the evaluation logic to a fast heuristic.
Debugging, testing, and observability tips
- Unit test evaluators with mocked AgentResponse objects. Keep evaluator logic pure when possible so it’s easy to test.
- Mock provider clients for integration tests so you can simulate iteration sequences and failures deterministically.
- Log a concise iteration record: {iteration, promptFingerprint, outputHash, evaluatorDecision, duration, costEstimate}.
- Add correlation IDs across iterations for traceability.
Troubleshooting common failure modes
- Marker never appended → verify prompt instruction clarity, or switch to judge evaluator.
- Judge flip-flops (YES/NO across iterations) → add hysteresis: require two consecutive YES judgments, or use a heuristic plus judge.
- Side effects duplicated → make tools idempotent or track operation IDs.
- Loop stalls (no progress) → add progress metrics and a stuck detection rule (stop if no semantic change after N iterations).
Real-world scenario examples
- Document extraction and QA: agent extracts structured data, runs validators, requests corrections until extraction passes validators or MaxIterations reached.
- Customer support automation: agent composes reply drafts and refines them until a judge model or human reviewer marks them acceptable.
Conclusion and next steps
Loop programming is a practical, powerful pattern for agentic AI: agent acts, inspects results, evaluates, and repeats — but only when bounded and observable. The Microsoft Agent Framework offers loop wrapper and evaluator patterns to implement this safely; adapt the example above to your MAF version and provider.
BlogWriter
My demo program, BlogWriter, uses a much more deterministic approach. While the author does pass its draft to the reviewer for evaluation, and they do create a small loop, a true looping approach would, perhaps, return better results. Something to try.
More to come soon…





