Skip to content

Python: Add message injection middleware#6998

Draft
eavanvalkenburg wants to merge 1 commit into
microsoft:mainfrom
eavanvalkenburg:message_injection
Draft

Python: Add message injection middleware#6998
eavanvalkenburg wants to merge 1 commit into
microsoft:mainfrom
eavanvalkenburg:message_injection

Conversation

@eavanvalkenburg

@eavanvalkenburg eavanvalkenburg commented Jul 8, 2026

Copy link
Copy Markdown
Member

Motivation & Context

Python needs a native way to inject messages into an active agent run while the function-calling loop is in progress, matching the scenario covered by the .NET message-injecting chat client without introducing a chat-client wrapper pattern into Python.

This enables tools or host code to enqueue additional session messages while a long-running tool is executing. The queued messages are drained into the next model call for the same AgentSession, so the final answer can account for the new input without starting a separate run.

This PR is draft because it depends on #6997. Until informational-only function call content is available, message injection treats all function-call content as actionable; a TODO marks the follow-up point where informational-only hosted/provider-executed calls should be ignored by the follow-up detection.

Description & Review Guide

  • What are the major changes?
    • Adds MessageInjectionMiddleware, enqueue_messages(session, messages), and MESSAGE_INJECTION_PENDING_MESSAGES_STATE_KEY to the Python core API.
    • Passes the active AgentSession through ChatContext so chat middleware can coordinate with session-scoped state without forwarding the session to leaf chat clients.
    • Keeps function invocation in control when a model response contains function calls, while allowing injected messages to trigger another model call when no function calls need handling.
    • Adds focused core tests for prequeued messages, queued messages after a call, tool-queued messages, streaming, and missing-session behavior.
    • Adds a Foundry-backed sample showing message injection while an async tool is sleeping.
    • Consolidates Python sample guidance into python/samples/AGENTS.md and removes the duplicate python-samples skill.
  • What is the impact of these changes?
    • Python callers get a session-only enqueue API for active-run message injection.
    • Existing chat clients continue to receive no session kwarg at the leaf layer.
    • The PR is additive and not intended to be breaking.
  • What do you want reviewers to focus on?
    • Whether the middleware belongs in _sessions.py with other session-scoped chat middleware.
    • Whether the loop boundary with function invocation is correct for both non-streaming and streaming paths.
    • Whether the public names (enqueue_messages and MESSAGE_INJECTION_PENDING_MESSAGES_STATE_KEY) are the right API shape.

Related Issue

Fixes #6996.

Depends on #6997.

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. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 13:51
@giles17 giles17 added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _middleware.py4001895%61, 64, 69, 357, 369, 910, 926, 928, 930, 1063, 1066, 1093, 1095, 1231, 1235, 1417, 1421, 1489
   _sessions.py4744191%117–119, 121–122, 139–140, 142–144, 221–222, 312, 575–579, 594, 624, 661–662, 676, 678, 697, 699, 782, 788, 822, 893, 897, 907, 1040, 1056, 1189, 1203–1204, 1227, 1249, 1259, 1301
   _tools.py11558292%226–227, 404, 406, 419, 444–446, 454, 472, 486, 493, 500, 523, 525, 532, 540, 669, 703–705, 708–710, 712, 718, 769–771, 796, 822, 826, 864–866, 870, 892, 1043, 1055, 1062–1065, 1086, 1094, 1108–1110, 1460, 1552, 1580, 1602, 1610, 1699, 1706–1707, 1766, 1770, 1816, 1877–1878, 1889, 1961, 1975, 1978, 1991, 1994, 2017, 2024, 2034, 2038, 2115, 2168, 2190, 2246, 2325, 2522, 2588–2589, 2755–2756, 2847
TOTAL44572535487% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
8821 33 💤 0 ❌ 0 🔥 2m 11s ⏱️

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 4 | Confidence: 83%

✓ Correctness

The implementation is well-structured and correct. The MessageInjectionMiddleware properly coordinates with the function invocation layer by yielding control when function calls are present, and only loping internally when messages are queued without pending function calls. Thread safety is appropriate for the brief list operations. The session propagation through the middleware and tool layers is clean—session is extracted at the middleware layer and placed on ChatContext without leaking to the leaf chat client. No correctness issues found.

✓ Security Reliability

The MessageInjectionMiddleware implementation is well-structured with proper thread-safe locking, correct session flow through the middleware/tool layers, and clear error handling for missing sessions. The main reliability concern is the unbounded while True loops in the non-streaming and streaming paths — unlike the function invocation layer which has a DEFAULT_MAX_ITERATIONS = 40 guard, the message injection loops have no iteration limit. A misbehaving downstream component that continuously enqueues messages could cause runaway API calls.

✓ Test Coverage

The test suite for MessageInjectionMiddleware is well-structured with focused tests for the key non-streaming scenarios (pre-queued messages, loop-on-queue, tool-enqueued deferral), the streaming loop path, session-required validation, and state-key storage. The primary test coverage gap is the absence of a streaming-mode test where the response contains function calls—a distinct code path in _stream_injected_messages (line 704) that should NOT loop, validated only indirectly by the non-streaming tool test. A minor gap is the lack of a test for accumulating multiple enqueue_messages calls before drain.

✓ Design Approach

I found one blocking design issue in the new streaming test coverage. The PR currently codifies a queued-message loop that only works when the chat service stores history server-side; for the framework’s default stateless client model, the second streamed model call would lose the prior transcript. Existing function-loop code in the repo already handles this distinction, so the new test is locking in the wrong behavior for one important client class.

Suggestions

  • Consider adding a configurable max-iteration guard to the while True loops in _process_non_streaming and _stream_injected_messages, similar to DEFAULT_MAX_ITERATIONS = 40 in the function invocation layer (_tools.py:90). This provides defense-in-depth against runaway API calls if a caller bug continuously enqueues messages.

Automated review by eavanvalkenburg's agents

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.

Pull request overview

Adds a Python-native “message injection” mechanism that lets host code/tools enqueue additional messages into an active AgentSession and have them automatically included in the next model call within the same run (including during streaming), without introducing a chat-client wrapper pattern.

Changes:

  • Introduces MessageInjectionMiddleware, enqueue_messages(session, ...), and MESSAGE_INJECTION_PENDING_MESSAGES_STATE_KEY, enabling session-scoped message queueing and draining between model calls.
  • Plumbs AgentSession through ChatContext (middleware-visible only) so chat middleware can coordinate with session state without forwarding session to leaf chat clients.
  • Adds focused core tests and a Foundry-backed middleware sample; consolidates sample guidelines into python/samples/AGENTS.md and removes the duplicate python-samples skill.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
python/samples/AGENTS.md Consolidates sample guidance; updates Foundry snippet to construct Agent(...) explicitly.
python/samples/02-agents/middleware/README.md Adds the new message-injection sample to the middleware index.
python/samples/02-agents/middleware/message_injection_middleware.py New sample demonstrating injecting a follow-up user message while a long-running tool is awaiting.
python/packages/core/tests/core/test_middleware_with_chat.py Adds tests covering prequeued messages, post-call queueing, tool-queued messages, streaming, and missing-session behavior.
python/packages/core/AGENTS.md Documents MessageInjectionMiddleware as a session-scoped middleware concept.
python/packages/core/agent_framework/_tools.py Ensures session remains available to middleware layers by no longer stripping it early.
python/packages/core/agent_framework/_sessions.py Implements session-state queueing and the MessageInjectionMiddleware loop behavior.
python/packages/core/agent_framework/_middleware.py Adds session to ChatContext and extracts it from client_kwargs for middleware use.
python/packages/core/agent_framework/init.pyi Exposes new public API symbols in typing stubs.
python/packages/core/agent_framework/init.py Exposes new public API symbols at runtime via lazy exports.
python/AGENTS.md Removes the python-samples skill reference from Python agent instructions.
python/.github/skills/python-samples/SKILL.md Deletes the duplicated samples skill (now covered by python/samples/AGENTS.md).

Comment on lines +639 to +649
def _drain_pending_messages(self, session: AgentSession, messages: Sequence[Message]) -> list[Message]:
with _MESSAGE_INJECTION_LOCK:
queue = cast(
list[Message],
session.state.setdefault(MESSAGE_INJECTION_PENDING_MESSAGES_STATE_KEY, []),
)
if not queue:
return list(messages)
next_messages = [*messages, *queue]
queue.clear()
return next_messages
Comment thread python/samples/AGENTS.md
Comment on lines +111 to +115
## File structure

Every sample file follows this order:

1. PEP 723 inline script metadata (if external dependencies are needed)
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.

Python: Support message injection for chat flows

4 participants