Skip to content

Python: add signal protocol for AgentLoopMiddleware autonomous loops - #9162

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

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

Conversation

@marklicata

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

Copy link
Copy Markdown

Motivation & Context

Agents running inside AgentLoopMiddleware have no standard way to say "I'm done" or "I need a human". Each team writes its own should_continue predicate. 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 Python half of the work previously proposed in #8993, split per language at the reviewers' request. The .NET counterpart is #9163.

Description & Review Guide

What are the major changes?

  • SignalParser / signal_should_continue() (agent_framework._harness._signals): drop-in should_continue predicate for AgentLoopMiddleware. It scans assistant messages only, so 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 the exit reason from assistant text first, then additional_properties; a signal always wins over the iteration cap
  • AgentLoopMiddleware now stamps additional_properties["loop_exit_reason"] = "iteration_cap_reached" when the cap fires (streaming and non-streaming)
  • New public symbols exported from agent_framework; __init__.pyi and packages/core/AGENTS.md updated

What is the impact of these changes?

Additive and marked experimental. Loops that don't use SignalParser see no behavior change beyond the new loop_exit_reason stamp when the cap fires.

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
  • Token strings are case-sensitive and must match the .NET implementation exactly

Related Issue

Part of #8860, together with #9163 for .NET. 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 balanced review requested due to automatic review settings October 7, 2026 15:34
@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 labels Oct 7, 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.

🟡 Changes recommended

Exit metadata can remain stale, and the public API does not consistently match its documented experimental scope and exit-reason contract.

4 open findings
What changed in this PR

Adds a Python signal protocol for autonomous AgentLoopMiddleware termination and typed exit-reason reporting.

Changes:

  • Adds task-complete and human-input signal parsing.
  • Stamps iteration-cap metadata for streaming and non-streaming loops.
  • Exports and tests the new public APIs.
File Description
test_harness_loop.py Tests signal parsing and loop exits.
AGENTS.md Documents the signal protocol.
_signals.py Implements signals and exit reasons.
_loop.py Stamps iteration-cap metadata.
__init__.pyi Exposes typed public imports.
__init__.py Adds runtime public exports.

🧠 Review effort: Balanced


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

…imental markers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per review feedback, fold the small signal protocol (LoopExitReason,
SignalParser, signal_should_continue, get_loop_exit_reason and the token /
exit-reason-key constants) into _harness/_loop.py alongside
AgentLoopMiddleware and delete _harness/_signals.py. The middleware now
stamps the cap reason via the shared LOOP_EXIT_REASON_KEY /
LoopExitReason.iteration_cap_reached constants. Public exports are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mark Licata (marklicata) added a commit to marklicata/agent-framework that referenced this pull request Oct 13, 2026
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>
TASK_COMPLETE: and NEED_INPUT: now count as signals only when they start a
line (leading whitespace allowed) in an assistant message, so mid-sentence or
backtick-quoted mentions no longer stop the loop or stamp an exit reason.
SignalParser and get_loop_exit_reason share one detection helper.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# A token only counts when it starts a line (leading spaces/tabs allowed), so a mid-sentence or
# backtick-quoted mention such as "I'll emit `TASK_COMPLETE:` when done" is not a signal.
# The ``text`` group captures the rest of the token's line.
_TASK_COMPLETE_PATTERN = re.compile(rf"^[ \t]*{re.escape(TASK_COMPLETE_TOKEN)}(?P<text>[^\n]*)", re.MULTILINE)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This start-of-line rule still treats a token inside a fenced Markdown code block as a real signal. For example, an assistant explaining the protocol with a fenced TASK_COMPLETE: example line terminates the loop. Please exclude fenced-code regions, or otherwise require the signal to occur outside code, and add fenced TASK_COMPLETE: and NEED_INPUT: regressions.

…g to sample

- Remove public SignalParser, signal_should_continue, get_loop_exit_reason, LoopExitReason APIs
- Keep framework-owned stop-reason stamping in additional_properties:
  iteration_cap_reached (cap fires) and completed (judge answers)
- Judge callback stamps completed when answered=True
- Add signal_protocol.py sample with full TASK_COMPLETE:/NEED_INPUT: parsing
- Addresses eavanvalkenburg feedback on microsoft#8860

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
- Clear loop_exit_reason from result at start of each iteration so inner
  provider or nested-loop values cannot mask the outer loop's decision
- Judge completed: use direct assignment (not setdefault) so stale values
  do not block it
- Cap iteration_cap_reached: use setdefault so judge's completed survives
  if both ever fire on the same final iteration
- Add tests for stale-reason-clearing (with judge, and with no outer reason)

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
github-app-auth — e08916d9 Deployed Nov 4, 2026 by marklicata via add_label #27024
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 python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants