镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Python: Python + .NET: add signal protocol for autonomous agent loops - #8993

Draft
Mark Licata (marklicata) wants to merge 2 commits into
microsoft:mainfrom
marklicata:agent-signal-protocol
Draft

Mark Licata (marklicata) wants to merge 2 commits into
microsoft:mainfrom
marklicata:agent-signal-protocol

Conversation

@marklicata

Copy link
Copy Markdown

Motivation & Context

Closes #8860

Adds a text-based signal protocol so agents running inside AgentLoopMiddleware (Python) or LoopAgent (.NET) can communicate loop termination intent without custom predicate code per team.

Two tokens, emitted by the agent in its response text:

  • TASK_COMPLETE: <summary> — agent signals it is done; loop stops
  • NEED_INPUT: <question> — agent needs human input; loop stops

Description & Review Guide

What are the major changes?

Python (agent_framework._harness._signals):

  • SignalParser / signal_should_continue() — drop-in should_continue predicate for AgentLoopMiddleware; scans assistant messages only (user-role nudges injected between iterations are ignored)
  • LoopExitReason — typed exit reason strings: "completed", "iteration_cap_reached", "need_input", "cancelled"
  • get_loop_exit_reason(response) — reads exit reason from assistant text first, then additional_properties; signal always wins over the iteration cap
  • AgentLoopMiddleware now stamps additional_properties["loop_exit_reason"] = "iteration_cap_reached" when the cap fires (both streaming and non-streaming paths)
  • 8 new public symbols exported; __init__.pyi updated; CHANGELOG.md and AGENTS.md updated

.NET (Microsoft.Agents.AI):

  • SignalProtocolLoopEvaluator — LoopEvaluator matching the Python token convention; stamps context.AdditionalProperties["loop_exit_reason"] before returning Stop()
  • LoopExitReason — static class with const string values and AdditionalPropertiesKey; XML docs note that exit reason stamping applies to non-streaming only
  • LoopAgent (non-streaming): stamps cap and evaluator-stop exit reasons on the returned AgentResponse.AdditionalProperties
  • PublicAPI.Unshipped.txt updated for all 5 target frameworks

What is the impact of these changes?

No breaking changes. All new API is additive and marked [Experimental]. Teams not using SignalParser / SignalProtocolLoopEvaluator see no behavior change.

What do you want reviewers to focus on?

  • The two-source lookup order in get_loop_exit_reason: assistant text first, additional_properties second (so a signal on the final allowed iteration is reported as completed, not iteration_cap_reached)
  • The .NET streaming limitation noted in LoopExitReason XML docs — exit reason is only stamped on non-streaming responses
  • Token strings are case-sensitive and must match exactly between Python and .NET

Related Issue

Closes #8860

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change.

Introduces a text-based signal convention so agents can communicate
loop termination intent without custom predicate code per team.

Signal tokens (case-sensitive, scanned from assistant messages only):
  TASK_COMPLETE: <summary>   -- agent signals completion, loop stops
  NEED_INPUT: <question>     -- agent needs human input, loop stops

New module: agent_framework._harness._signals
- SignalParser: drop-in should_continue predicate for AgentLoopMiddleware
- LoopExitReason: typed exit reason strings matching FinishReason convention
  ("completed", "iteration_cap_reached", "need_input", "cancelled")
- signal_should_continue(): factory returning a SignalParser instance
- get_loop_exit_reason(response): reads assistant text first, then falls
  back to additional_properties; signal always wins over iteration cap
- LOOP_EXIT_REASON_KEY, LOOP_NEED_INPUT_KEY: documented convention keys

AgentLoopMiddleware (_loop.py):
- Stamps loop_exit_reason='iteration_cap_reached' in additional_properties
  when max_iterations fires (both streaming and non-streaming paths)
- No import of _signals; uses string literal to keep modules decoupled

Exports: 8 new public symbols in agent_framework namespace + __init__.pyi
CHANGELOG.md and python/packages/core/AGENTS.md updated.

Tests: 117 total (17 new signal protocol tests covering unit, integration,
streaming cap, and nudge-text false-positive regression).

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Introduces SignalProtocolLoopEvaluator and LoopExitReason to match
the Python signal protocol, using the same token strings and
AdditionalProperties key so tooling can treat both languages uniformly.

New types (Microsoft.Agents.AI namespace):
- SignalProtocolLoopEvaluator: LoopEvaluator that scans LastResponse.Text
  for TASK_COMPLETE: and NEED_INPUT: tokens. Stamps context.AdditionalProperties
  before returning Stop() so the exit reason survives into the response.
- LoopExitReason: static class with const string values and AdditionalPropertiesKey
  ("loop_exit_reason") matching the Python convention.

LoopAgent (non-streaming path):
- Stamps LoopExitReason.IterationCapReached when max_iterations fires.
- Copies exit reason from context.AdditionalProperties to the returned
  AgentResponse when an evaluator stops the loop.

Note: streaming path does not stamp AdditionalProperties (the streaming
response is assembled by the caller from yielded updates; there is no
single response object to stamp inside the generator). Non-streaming
is fully covered. See LoopExitReason XML docs.

PublicAPI.Unshipped.txt updated for all target frameworks.

Tests: 3343 total (11 new SignalProtocolLoopEvaluatorTests, all passing).

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 2, 2026 23:07
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python .NET Usage: [Issues, PRs], Target: .Net labels Oct 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 1 High severity · 6 Medium severity · 1 Low severity

Open (8)
What changed in this PR

Adds a cross-language (Python + .NET) text-based “signal protocol” for autonomous agent loops so agents can stop looping by emitting TASK_COMPLETE: or NEED_INPUT: tokens, and surfaces a typed loop-exit reason for consumers.

Changes:

  • Python: introduces _harness._signals utilities (SignalParser, signal_should_continue, LoopExitReason, get_loop_exit_reason) and stamps loop_exit_reason=iteration_cap_reached when the iteration cap fires (streaming + non-streaming).
  • .NET: adds SignalProtocolLoopEvaluator + LoopExitReason constants and stamps loop_exit_reason for non-streaming LoopAgent results and evaluator-driven stops.
  • Updates tests, public exports/API listings, and docs/changelog.
File Description
python/​packages/​core/​agent_framework/​_harness/​_signals.py Adds signal-token parsing + loop-exit reason derivation helper.
python/​packages/​core/​agent_framework/​_harness/​_loop.py Stamps loop_exit_reason when max-iteration cap fires (streaming + non-streaming).
python/​packages/​core/​tests/​core/​test_harness_loop.py Adds Python unit tests for signal parsing and exit-reason behavior.
python/​packages/​core/​agent_framework/​__init__.py Re-exports new public signal-protocol symbols.
python/​packages/​core/​agent_framework/​__init__.pyi Adds typing exports for new public signal-protocol symbols.
python/​packages/​core/​AGENTS.md Documents the new signal protocol and exit-reason semantics.
python/​CHANGELOG.md Notes the new signal protocol and exit-reason stamping behavior.
dotnet/​src/​Microsoft.Agents.AI/​Harness/​Loop/​SignalProtocolLoopEvaluator.cs Adds evaluator implementing the token convention and stamping exit reason in context properties.
dotnet/​src/​Microsoft.Agents.AI/​Harness/​Loop/​LoopExitReason.cs Adds typed exit-reason constants + docs about non-streaming stamping.
dotnet/​src/​Microsoft.Agents.AI/​Harness/​Loop/​LoopAgent.cs Stamps exit reason on non-streaming results for cap-reached and evaluator-stop scenarios.
dotnet/​src/​Microsoft.Agents.AI/​Harness/​Loop/​LoopEvaluator.cs Updates docs to include SignalProtocolLoopEvaluator as an OOTB evaluator.
dotnet/​tests/​Microsoft.Agents.AI.UnitTests/​Harness/​Loop/​SignalProtocolLoopEvaluatorTests.cs Adds unit tests for evaluator stop/continue behavior and stamping.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​net472/​PublicAPI.Unshipped.txt Declares new public APIs for net472.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​netstandard2.0/​PublicAPI.Unshipped.txt Declares new public APIs for netstandard2.0.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​net8.0/​PublicAPI.Unshipped.txt Declares new public APIs for net8.0.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​net9.0/​PublicAPI.Unshipped.txt Declares new public APIs for net9.0.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​net10.0/​PublicAPI.Unshipped.txt Declares new public APIs for net10.0.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 202 to 208
if (iteration >= this._maxIterations)
{
this.LogMaxIterationsReached(iteration);
return this.BuildResult(response, transcript, aggregatedUsage);
AgentResponse capResult = this.BuildResult(response, transcript, aggregatedUsage);
(capResult.AdditionalProperties ??= new())[LoopExitReason.AdditionalPropertiesKey] = LoopExitReason.IterationCapReached;
return capResult;
}
Comment on lines +51 to +63
string text = context.LastResponse.Text;

if (text.Contains(TaskCompleteToken))
{
context.AdditionalProperties[LoopExitReason.AdditionalPropertiesKey] = LoopExitReason.Completed;
return new ValueTask<LoopEvaluation>(LoopEvaluation.Stop());
}

if (text.Contains(NeedInputToken))
{
context.AdditionalProperties[LoopExitReason.AdditionalPropertiesKey] = LoopExitReason.NeedInput;
return new ValueTask<LoopEvaluation>(LoopEvaluation.Stop());
}
Comment on lines +606 to +607
if cap_fired and final_result is not None:
final_result.additional_properties.setdefault("loop_exit_reason", "iteration_cap_reached")
Comment on lines +716 to +717
if cap_fired and holder["final"] is not None:
holder["final"].additional_properties.setdefault("loop_exit_reason", "iteration_cap_reached")
Comment on lines +164 to +168
assistant_text = "".join(m.text or "" for m in response.messages if m.role == "assistant")
if TASK_COMPLETE_TOKEN in assistant_text:
return LoopExitReason.completed
if NEED_INPUT_TOKEN in assistant_text:
return LoopExitReason.need_input

assert client.call_count == 1
assert get_loop_exit_reason(response) == LoopExitReason.completed
assert LOOP_EXIT_REASON_KEY not in response.additional_properties

assert client.call_count == 1
assert get_loop_exit_reason(response) == LoopExitReason.need_input
assert LOOP_EXIT_REASON_KEY not in response.additional_properties
response = await agent.run("task")

assert client.call_count == 2
assert response.additional_properties.get("loop_exit_reason") == LoopExitReason.iteration_cap_reached
@github-actions github-actions Bot changed the title Python + .NET: add signal protocol for autonomous agent loops .NET: Python + .NET: add signal protocol for autonomous agent loops Oct 2, 2026
@github-actions github-actions Bot changed the title .NET: Python + .NET: add signal protocol for autonomous agent loops Python: Python + .NET: add signal protocol for autonomous agent loops Oct 2, 2026

This branch was successfully deployed

1 active deployment
github-app-auth — a34c84f5 Deployed Oct 2, 2026 by marklicata via team_check #5897
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs .NET Usage: [Issues, PRs], Target: .Net python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add signal-based completion detection and typed exit reasons to AgentLoopMiddleware

2 participants