Skip to content

.NET: add SignalProtocolLoopEvaluator for LoopAgent - #9163

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

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

Conversation

@marklicata

@marklicata Mark Licata (marklicata) commented Oct 7, 2026 •

Copy link
Copy Markdown

Motivation & Context

Agents running inside LoopAgent have no standard way to say "I'm done" or "I need a human". Each team writes its own LoopEvaluator. This adds a text-based signal protocol so the agent can communicate loop termination intent directly in its response text:

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

This is the .NET half of the work previously proposed in #8993, split per language at the reviewers' request. The Python counterpart is #9162 and uses the same token strings and loop_exit_reason key.

Description & Review Guide

What are the major changes?

  • SignalProtocolLoopEvaluator: a LoopEvaluator matching the token convention (ordinal comparison); stamps context.AdditionalProperties["loop_exit_reason"] before returning Stop()
  • LoopExitReason: [Experimental] static class with const string values and AdditionalPropertiesKey
  • 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?

Additive and marked [Experimental]. Consumers not using SignalProtocolLoopEvaluator see no behavior change beyond the exit-reason stamp on non-streaming responses.

What do you want reviewers to focus on?

  • Exit reason stamping applies to non-streaming responses only (documented in the LoopExitReason XML docs)
  • Cap vs. signal precedence: when the cap fires on an iteration whose text also contains a signal, LoopAgent currently reports iteration_cap_reached (carried over from an open review thread on Python: Python + .NET: add signal protocol for autonomous agent loops #8993)
  • Token strings are case-sensitive and must match the Python implementation exactly

Related Issue

Part of #8860, together with #9162 for Python. The issue is closed manually once both PRs have merged.

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.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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.

🟡 Changes recommended

Signal matching includes non-assistant messages, and stale exit reasons can leak across iterations.

3 open findings
What changed in this PR

Adds typed signal-based termination for .NET LoopAgent.

Changes:

  • Adds SignalProtocolLoopEvaluator and LoopExitReason.
  • Propagates non-streaming loop exit reasons.
  • Adds evaluator tests and public API baselines.
File Description
dotnet/​src/​Microsoft.Agents.AI/​Harness/​Loop/​SignalProtocolLoopEvaluator.cs Implements signal detection.
dotnet/​src/​Microsoft.Agents.AI/​Harness/​Loop/​LoopExitReason.cs Defines exit-reason constants.
dotnet/​src/​Microsoft.Agents.AI/​Harness/​Loop/​LoopEvaluator.cs Documents the new evaluator.
dotnet/​src/​Microsoft.Agents.AI/​Harness/​Loop/​LoopAgent.cs Propagates exit metadata.
dotnet/​tests/​Microsoft.Agents.AI.UnitTests/​Harness/​Loop/​SignalProtocolLoopEvaluatorTests.cs Tests signal evaluation.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​net8.0/​PublicAPI.Unshipped.txt Records net8.0 APIs.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​net9.0/​PublicAPI.Unshipped.txt Records net9.0 APIs.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​net10.0/​PublicAPI.Unshipped.txt Records net10.0 APIs.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​net472/​PublicAPI.Unshipped.txt Records net472 APIs.
dotnet/​src/​Microsoft.Agents.AI/​PublicAPI/​netstandard2.0/​PublicAPI.Unshipped.txt Records netstandard2.0 APIs.

🧠 Review effort: Balanced


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

Comment thread dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgent.cs
Comment thread dotnet/src/Microsoft.Agents.AI/Harness/Loop/SignalProtocolLoopEvaluator.cs Outdated
Comment thread dotnet/src/Microsoft.Agents.AI/Harness/Loop/LoopAgent.cs
…exit reason

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SignalProtocolLoopEvaluator now only recognizes TASK_COMPLETE: / NEED_INPUT:
when the token starts a line of an assistant message (optionally preceded by
spaces or tabs). Mid-sentence or backtick-quoted mentions such as
"I'll emit `TASK_COMPLETE:` when done" no longer stop the loop. Matches the
Python signal protocol (microsoft#9162).

DefaultFeedbackMessage now tells the model to start a new line with the token;
the PublicAPI.Unshipped.txt entries are updated because the analyzer tracks
const values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-dotnet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active and 1 inactive deployments
github-app-auth — c7984032 Deployed Oct 13, 2026 by marklicata via add_label #25198
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

.NET Usage: [Issues, PRs], Target: .Net

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants