Repository navigation
Description
Summary
AgentLoopMiddleware supports two completion detection mechanisms today: a developer-supplied should_continue predicate, and a judge-driven model that evaluates whether the original request was answered. Neither produces typed exit reasons — the caller cannot distinguish "agent finished the task" from "iteration cap fired" from "cancelled externally."
This issue proposes adding a third, lighter completion mechanism (text signal scanning) and typed exit reasons to the existing AgentLoopMiddleware in both the Python and .NET implementations.
We intend to take on implementation.
User need
When a user hands a task to an autonomous agent, they need a clear answer to whether it completed successfully. Today, every autonomous loop exit looks the same to the caller. Users also need a standard way for an agent to pause and surface a specific question — "I need your decision on X before I can continue" — without the loop terminating entirely.
Developer need
Teams building autonomous agents on MAF reimplement completion detection from scratch. The existing should_continue interface supports this, but there is no standard implementation. Building a signal-scanner predicate is simple; not having a canonical one means every team invents their own convention, and none of them are interoperable.
The default max_iterations=10 (Python) is appropriate for judge-driven quality refinement loops. For autonomous task-completion loops, this default silently terminates most real tasks before they complete. Guidance is needed.
What's already there
- Python:
AgentLoopMiddleware(python/packages/core/agent_framework/_harness/_loop.py) — full loop middleware withshould_continuepredicate,max_iterationssafety cap (default 10), judge-driven completion, feedback log, session snapshot/restore, and nudge injection. - .NET:
AgentLoopMiddlewareequivalent indotnet/src/Microsoft.Agents.AI/Harness/Loop/anddotnet/src/Microsoft.Agents.AI.Harness/. - Both: The loop already injects a nudge asking the agent to say when it is complete. There is no detection of that signal in either implementation.
Proposal
1. signal_should_continue helper — a ~50-line should_continue predicate implementation that scans the agent's last response text for two tokens:
TASK_COMPLETE: <summary> → returns False (stop), exit reason = "completed"
NEED_INPUT: <question> → pauses loop, surfaces question, waits for human resume()
(no signal, no tools) → returns True (continue), nudge injected
2. Typed loop exit reason — a LoopExitReason (enum or string literal union) surfaced on the response returned by the loop:
| Exit reason | Trigger |
|---|---|
completed |
Signal detected or judge verdict answered=True |
iteration_cap_reached |
max_iterations fired |
budget_exceeded |
Token cap hit (if budget integration is added separately) |
time_exceeded |
Wall-clock cap hit (if budget integration is added separately) |
cancelled |
External cancellation |
3. Documentation guidance on max_iterations — explicitly document that the default is calibrated for short quality-refinement loops. For autonomous task-completion loops, 50–150 is the appropriate range.
Scope
- Both Python and .NET implementations are in scope. The Python harness (
python/packages/core/agent_framework/_harness/) and .NET harness (dotnet/src/Microsoft.Agents.AI/Harness/anddotnet/src/Microsoft.Agents.AI.Harness/) maintain feature parity; changes to one require equivalent changes in the other. - New
signal_should_continuefunction andLoopExitReasontype (experimental-flagged in both languages) - No new packages, no new dependencies
Notes
The signal convention (TASK_COMPLETE: / NEED_INPUT:) is drawn from a production runtime where it ran at scale. The implementation is a should_continue predicate that slots into the existing loop interface — it does not require changes to the loop's core mechanics in either language.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
[-]Add signal-based completion detection and typed exit reasons to AgentLoopMiddleware[/-][+]Python: Add signal-based completion detection and typed exit reasons to AgentLoopMiddleware[/+][-]Python: Add signal-based completion detection and typed exit reasons to AgentLoopMiddleware[/-][+]Add signal-based completion detection and typed exit reasons to AgentLoopMiddleware[/+]eavanvalkenburg commentedon Nov 2, 2026
Mark Licata (@marklicata) I would like to narrow the Python implementation to a sample for now, with a small core change to report and preserve loop-exit metadata.
AgentResponse.additional_propertiesalready gives us the structure we need. Our own judge callback can setloop_exit_reason = "completed"when the judge says the task is answered. The loop can set"iteration_cap_reached"when its cap stops execution. A sampleshould_continuecallback can teach and parse theTASK_COMPLETE:/NEED_INPUT:convention, set"completed"or"need_input"on the response, and returnFalse. The question can stay in the response text, and the calling application keeps control of how it collects input and resumes the conversation. Cancellation should continue to propagate normally.There is one small core detail to address: the current non-streaming loop collects response metadata before calling
should_continue, then uses that earlier copy on the aggregated response. We should preserve metadata changes made by the callback, consistently for aggregated responses and streaming final responses. The sample should also explain the iteration-cap precedence: the cap currently skipsshould_continue, so any sample-specific rule that a signal on the last allowed iteration wins needs explicit final-response classification.This keeps the text tokens, parsing rules, and quoting/code-fence handling in the sample rather than making them a framework contract. We do not need public
SignalParser,signal_should_continue(),LoopExitReason, orget_loop_exit_reason()APIs for this first version. Core coverage should focus on metadata preservation and framework-owned stop reasons; the sample should cover its parsing behavior and explain what to set when.Especially for Python, this is enough structure for a v0. Once we get feedback from people using it, and there is demand for a more extensive version, we can reconsider a more complete integration. I would rather establish that need through usage than commit to the full public API now. This proposes a narrower Python scope for #9162; it is not a request to treat all of the original cross-language issue requirements as delivered.
Python: narrow signal protocol to framework stop reasons; move parsin…