Skip to content

Python: add agent-framework-hosting-activity-protocol channel - #5641

Merged
Eduard van Valkenburg (eavanvalkenburg) merged 5 commits into
microsoft:feature/python-hostingfrom
eavanvalkenburg:feat/hosting-channel-activity-protocol
May 28, 2026
Merged

Python: add agent-framework-hosting-activity-protocol channel#5641
Eduard van Valkenburg (eavanvalkenburg) merged 5 commits into
microsoft:feature/python-hostingfrom
eavanvalkenburg:feat/hosting-channel-activity-protocol

Conversation

@eavanvalkenburg

@eavanvalkenburgEduard van Valkenburg (eavanvalkenburg) commented May 5, 2026

Copy link
Copy Markdown
Member

Motivation and Context

Implements the Activity Protocol channel described in SPEC-002 §7 (merged via #5549). This is the lower-level Bot Service / Activity Protocol surface — it covers any Activity-Protocol-speaking client (Teams, Direct Line, Web Chat, Slack via Bot Service, etc.).

A separate PR-5b adds a higher-level agent-framework-hosting-teams package built on the microsoft-teams-apps SDK for Teams-native affordances (Adaptive Cards, Citations, streaming via ctx.stream).

Description

Adds agent-framework-hosting-activity-protocol (python/packages/hosting-activity-protocol/):

  • ActivityProtocolChannel — mounts POST /activity (configurable), validates Bot Framework JWT, decodes Activity Protocol payloads into ChannelRequest, replies as outbound activities.
  • Token validation + identity derivation from the Activity sender.
  • Tests for channel wiring, JWT path, and request/response shaping.

This is the rename target of the previously-named hosting-teams package — the directory rename frees hosting-teams for the Teams-SDK-based channel in PR-5b. TeamsChannelActivityProtocolChannel, name="teams""activity", default mount /teams/activity.

Stack

PR-5a of 10. Depends on #PR-2 (feat/hosting-core). Independent of PR-5b — the two Teams-related packages are intentionally separate (different audiences, see PR-5b for the comparison table).

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? No — new package (the previous hosting-teams package was never released).

@moonbox3Evan Mattson (moonbox3) added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels May 5, 2026

@github-actionsgithub-actionsBot 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: 2 | Confidence: 77%

✓ Test Coverage

The activity-protocol channel's most complex code path — _stream_to_conversation (~60 lines of concurrent async task logic with rate-limited edits) — has zero test coverage. The _make_teams helper accepts a stream parameter but no test ever passes stream=True, and the _FakeAgent fixture always returns a plain coroutine rather than a ResponseStream, meaning streaming cannot be exercised with the current test infrastructure. The run_hook and stream_transform_hook extensibility points are also untested. Non-streaming paths have reasonable coverage with meaningful assertions.

✓ Design Approach

No actionable issues found in this dimension.


Automated review by eavanvalkenburg's agents

@eavanvalkenburg
Eduard van Valkenburg (eavanvalkenburg)force-pushed the feat/hosting-channel-activity-protocol branch 2 times, most recently from 1a97b16 to e8616b7CompareMay 5, 2026 09:10
@eavanvalkenburg
Eduard van Valkenburg (eavanvalkenburg)force-pushed the feat/hosting-channel-activity-protocol branch from e8616b7 to f60466cCompareMay 7, 2026 14:28
@eavanvalkenburg
Eduard van Valkenburg (eavanvalkenburg)force-pushed the feature/python-hosting branch 2 times, most recently from 1a4caf6 to 25692a1CompareMay 22, 2026 11:54
@eavanvalkenburg
Eduard van Valkenburg (eavanvalkenburg)force-pushed the feat/hosting-channel-activity-protocol branch 2 times, most recently from 52d1f08 to 3aac006CompareMay 22, 2026 13:46
…vityProtocolChannel
The existing Bot-Framework-via-Azure-Bot-Service channel was previously
shipped under the name ``hosting-teams`` / ``TeamsChannel``. That name
is misleading for what the channel actually does -- it speaks the Bot
Framework Activity Protocol against Azure Bot Service, which fans out
across MS Teams, Slack, Webex, Telegram-via-Bot-Service, etc., and does
not provide any Teams-specific affordances.
This PR renames the package atomically and frees the ``hosting-teams``
name for a future Teams-native channel built on
``microsoft-teams-apps`` (PR-5b, spec req microsoft#28).
Renames (all in one commit):
- Package: ``agent-framework-hosting-teams`` ->
``agent-framework-hosting-activity-protocol``
- Module: ``agent_framework_hosting_teams`` ->
``agent_framework_hosting_activity_protocol``
- Channel class: ``TeamsChannel`` -> ``ActivityProtocolChannel``
- Helper: ``teams_isolation_key`` -> ``activity_protocol_isolation_key``
(isolation key prefix ``teams:`` -> ``activity:``)
- Channel name: ``"teams"`` -> ``"activity"``; default mount path
``/teams`` -> ``/activity``
- Internal helper: ``_parse_teams_activity`` -> ``_parse_activity``
- Worker task name + a couple of error strings updated for consistency
Updates README.md and the module docstring to call out:
- this is the channel-neutral Activity Protocol channel,
- it surfaces what every Bot-Service-connected channel has in common
(text in / text out),
- a forthcoming ``agent-framework-hosting-teams`` package will layer
Teams-specific affordances (adaptive cards, message extensions,
dialogs, SSO, ...) on the same Bot Service transport.
Workspace: registers ``agent-framework-hosting-activity-protocol`` in
``python/pyproject.toml`` and adds the matching pyright
``executionEnvironments`` entry.
Behavior is unchanged. Pyright + mypy clean, 11 tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- security (#3198327004): add `service_url_allowed_hosts` constructor
option (default `botframework.com` + `smba.trafficmanager.net`) and
reject inbound activities whose `serviceUrl` host falls outside it
with HTTP 400 — without this gate a malicious caller could redirect
outbound replies (and the attached bearer token) to an
attacker-controlled host
- security (#3198324219): add `inbound_auth_validator` async callback;
log a loud WARNING at startup when no validator AND no operator
reverse-proxy is configured so the dev-mode bypass cannot
accidentally ship to production. Document the contract: prototype
intentionally does not ship JWT validation (out of scope); operators
must plug a validator or terminate auth in front of the channel
- retry semantics (#3198328746): distinguish transient outbound
failures (httpx network errors, non-2xx from Bot Service) — return
502 so Bot Service retries — from deterministic agent failures —
return 200 so Bot Service does not retry the same broken activity
in a loop
- bug (#3198330424): fix the placeholder-failure deadlock. When
`send_initial_placeholder` fails, `activity_id` stays `None`, the
edit-worker loop exit condition (`accumulated == last_sent`) is
unreachable while no PUT is possible, and the worker would deadlock
on `wake.wait()` forever after `worker_done` is set. Now: skip the
worker entirely on placeholder failure and POST a single final
activity at the end with whatever accumulated
- tests (#3198334465, #3187178091, #3198336045): add coverage for
- `_is_service_url_allowed` allow/deny matrix + webhook 400 on
disallowed serviceUrl
- `inbound_auth_validator` allow/deny/raises paths
- outbound `Authorization: Bearer <token>` header presence in
production mode and absence in dev mode
- the streaming path (`_stream_to_conversation`): placeholder +
final edit, placeholder-failure fallback (with timeout guard
against deadlock regression), and empty-stream `(no response)`
placeholder replacement
- retry-signal differentiation: outbound `httpx.ConnectError` →
502; deterministic `ValueError` from the agent → 200
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
asyncio_mode = "auto" is configured in pyproject.toml across the
hosting packages, so individual @pytest.mark.asyncio decorators are
unnecessary.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eavanvalkenburg
Eduard van Valkenburg (eavanvalkenburg)force-pushed the feat/hosting-channel-activity-protocol branch from f8a963d to d2599aeCompareMay 28, 2026 12:33
@eavanvalkenburg
Eduard van Valkenburg (eavanvalkenburg) merged commit cdea9fa into microsoft:feature/python-hostingMay 28, 2026
2 checks passed
Eduard van Valkenburg (eavanvalkenburg) added a commit that referenced this pull request Jun 17, 2026
* feat(hosting-activity-protocol): rename Bot Framework channel to ActivityProtocolChannel
The existing Bot-Framework-via-Azure-Bot-Service channel was previously
shipped under the name ``hosting-teams`` / ``TeamsChannel``. That name
is misleading for what the channel actually does -- it speaks the Bot
Framework Activity Protocol against Azure Bot Service, which fans out
across MS Teams, Slack, Webex, Telegram-via-Bot-Service, etc., and does
not provide any Teams-specific affordances.
This PR renames the package atomically and frees the ``hosting-teams``
name for a future Teams-native channel built on
``microsoft-teams-apps`` (PR-5b, spec req #28).
Renames (all in one commit):
- Package: ``agent-framework-hosting-teams`` ->
``agent-framework-hosting-activity-protocol``
- Module: ``agent_framework_hosting_teams`` ->
``agent_framework_hosting_activity_protocol``
- Channel class: ``TeamsChannel`` -> ``ActivityProtocolChannel``
- Helper: ``teams_isolation_key`` -> ``activity_protocol_isolation_key``
(isolation key prefix ``teams:`` -> ``activity:``)
- Channel name: ``"teams"`` -> ``"activity"``; default mount path
``/teams`` -> ``/activity``
- Internal helper: ``_parse_teams_activity`` -> ``_parse_activity``
- Worker task name + a couple of error strings updated for consistency
Updates README.md and the module docstring to call out:
- this is the channel-neutral Activity Protocol channel,
- it surfaces what every Bot-Service-connected channel has in common
(text in / text out),
- a forthcoming ``agent-framework-hosting-teams`` package will layer
Teams-specific affordances (adaptive cards, message extensions,
dialogs, SSO, ...) on the same Bot Service transport.
Workspace: registers ``agent-framework-hosting-activity-protocol`` in
``python/pyproject.toml`` and adds the matching pyright
``executionEnvironments`` entry.
Behavior is unchanged. Pyright + mypy clean, 11 tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* review: address PR-5 round 2 feedback
- security (#3198327004): add `service_url_allowed_hosts` constructor
option (default `botframework.com` + `smba.trafficmanager.net`) and
reject inbound activities whose `serviceUrl` host falls outside it
with HTTP 400 — without this gate a malicious caller could redirect
outbound replies (and the attached bearer token) to an
attacker-controlled host
- security (#3198324219): add `inbound_auth_validator` async callback;
log a loud WARNING at startup when no validator AND no operator
reverse-proxy is configured so the dev-mode bypass cannot
accidentally ship to production. Document the contract: prototype
intentionally does not ship JWT validation (out of scope); operators
must plug a validator or terminate auth in front of the channel
- retry semantics (#3198328746): distinguish transient outbound
failures (httpx network errors, non-2xx from Bot Service) — return
502 so Bot Service retries — from deterministic agent failures —
return 200 so Bot Service does not retry the same broken activity
in a loop
- bug (#3198330424): fix the placeholder-failure deadlock. When
`send_initial_placeholder` fails, `activity_id` stays `None`, the
edit-worker loop exit condition (`accumulated == last_sent`) is
unreachable while no PUT is possible, and the worker would deadlock
on `wake.wait()` forever after `worker_done` is set. Now: skip the
worker entirely on placeholder failure and POST a single final
activity at the end with whatever accumulated
- tests (#3198334465, #3187178091, #3198336045): add coverage for
- `_is_service_url_allowed` allow/deny matrix + webhook 400 on
disallowed serviceUrl
- `inbound_auth_validator` allow/deny/raises paths
- outbound `Authorization: Bearer <token>` header presence in
production mode and absence in dev mode
- the streaming path (`_stream_to_conversation`): placeholder +
final edit, placeholder-failure fallback (with timeout guard
against deadlock regression), and empty-stream `(no response)`
placeholder replacement
- retry-signal differentiation: outbound `httpx.ConnectError` →
502; deterministic `ValueError` from the agent → 200
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(hosting): drop redundant @pytest.mark.asyncio decorators
asyncio_mode = "auto" is configured in pyproject.toml across the
hosting packages, so individual @pytest.mark.asyncio decorators are
unnecessary.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(hosting-activity-protocol): add response hooks
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(hosting-activity-protocol): mark constructor keyword args
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eavanvalkenburg
Eduard van Valkenburg (eavanvalkenburg) deleted the feat/hosting-channel-activity-protocol branch June 30, 2026 09:01
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationUsage: [Issues, PRs], Target: documentation in the code base and learn docspythonUsage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@eavanvalkenburg@moonbox3