Version: all microsoft-agents-* 1.3.0 (Python 3.12). Both defects verified unchanged on main as of 2026-08-12.
Environment: anonymous mode (CONNECTIONS__SERVICE_CONNECTION__SETTINGS__ANONYMOUS_ALLOWED=true) against the M365 Agents Playground (@microsoft/m365agentsplayground 0.2.28).
Summary
In anonymous mode, inbound activities work but any backend-initiated (proactive) continuation fails with ValueError: TENANT_ID is not set in the configuration. — process_proactive never computes the use_anonymous flag that process_activity forwards to the client factory. A second defect in the app.proactive storage round-trip loses the anonymous marker entirely, so the higher-level API would fail even with the adapter fixed.
Bug 1: process_proactive ignores anonymous identities
process_activity (channel_service_adapter.py:393-398) computes:
use_anonymous_auth_callback = False
if (
not claims_identity.is_authenticated
and claims_identity.authentication_type == "Anonymous"
):
use_anonymous_auth_callback = True
and passes it to create_user_token_client (:409-410) and create_connector_client (:422-428). process_proactive (:302-335) has no equivalent branch — it calls both factory methods with use_anonymous defaulted to False, so MSAL token acquisition runs and raises:
File ".../channel_service_adapter.py", line 318, in process_proactive
await self._channel_service_client_factory.create_user_token_client(
File ".../rest_channel_service_client_factory.py", line 181, in create_user_token_client
token = await token_provider.get_access_token(
File ".../msal_auth.py", line 224, in _resolve_tenant_id
raise ValueError("TENANT_ID is not set in the configuration.")
Bug 2: the proactive storage round-trip loses the anonymous marker
app/proactive/conversation.py:101:
return ClaimsIdentity(claims=dict(claims), is_authenticated=True)
identity_from_claims hardcodes is_authenticated=True, and _PERSISTED_CLAIM_KEYS (line 19) does not persist authentication_type — so an identity stored from an anonymous turn comes back authenticated and unrecoverable, routing down the MSAL path regardless of Bug 1.
Repro
Anonymous-mode agent; store the reference on message, continue two seconds later:
@AGENT_APP.activity("message")
async def on_message(context: TurnContext, _state: TurnState):
ref = context.activity.get_conversation_reference()
identity = context.identity
async def later():
await asyncio.sleep(2)
async def notify(ctx: TurnContext):
await ctx.send_activity("proactive follow-up")
await ADAPTER.continue_conversation_with_claims(
identity, ref.get_continuation_activity(), notify
)
asyncio.get_event_loop().create_task(later())
await context.send_activity("stored; follow-up in ~2s")
Connect the Playground, send any message: the inbound reply works, the continuation raises the traceback above. app.proactive.continue_conversation (storage round-trip) fails identically via Bug 2.
Suggested fix
In process_proactive, compute use_anonymous from the claims identity exactly as process_activity does and pass it to both factory calls. For Bug 2, persist authentication_type (or an is_authenticated marker) in claims_from_identity and honor it in identity_from_claims.
Related: PR #508 (closed, unmerged) addressed the inbound anonymous ClaimsIdentity but not the proactive path.
Workaround (verified against the Playground)
class AnonymousProactiveAdapter(CloudAdapter):
async def process_proactive(self, claims_identity, continuation_activity, audience, callback):
use_anonymous = (
not claims_identity.is_authenticated
and claims_identity.authentication_type == "Anonymous"
)
if not use_anonymous:
return await super().process_proactive(
claims_identity, continuation_activity, audience, callback
)
audience = audience or AuthenticationConstants.AGENTS_SDK_SCOPE
context = self._create_turn_context(claims_identity, audience, activity=continuation_activity)
user_token_client = await self._channel_service_client_factory.create_user_token_client(
context, claims_identity, True
)
context.services.set(UserTokenClientBase, user_token_client)
context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client
connector_client = await self._channel_service_client_factory.create_connector_client(
context, claims_identity, continuation_activity.service_url, audience, None, True
)
context.services.set(ConnectorClientBase, connector_client)
context.turn_state[self._AGENT_CONNECTOR_CLIENT_KEY] = connector_client
await self.run_pipeline(context, callback)
await connector_client.close()
await user_token_client.close()
Anonymous continuation then works; authenticated identities keep the stock path. (The claims identity is kept in process memory rather than via app.proactive storage, sidestepping Bug 2.)
Version: all
microsoft-agents-*1.3.0 (Python 3.12). Both defects verified unchanged onmainas of 2026-08-12.Environment: anonymous mode (
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__ANONYMOUS_ALLOWED=true) against the M365 Agents Playground (@microsoft/m365agentsplayground0.2.28).Summary
In anonymous mode, inbound activities work but any backend-initiated (proactive) continuation fails with
ValueError: TENANT_ID is not set in the configuration.—process_proactivenever computes theuse_anonymousflag thatprocess_activityforwards to the client factory. A second defect in theapp.proactivestorage round-trip loses the anonymous marker entirely, so the higher-level API would fail even with the adapter fixed.Bug 1:
process_proactiveignores anonymous identitiesprocess_activity(channel_service_adapter.py:393-398) computes:and passes it to
create_user_token_client(:409-410) andcreate_connector_client(:422-428).process_proactive(:302-335) has no equivalent branch — it calls both factory methods withuse_anonymousdefaulted toFalse, so MSAL token acquisition runs and raises:Bug 2: the proactive storage round-trip loses the anonymous marker
app/proactive/conversation.py:101:identity_from_claimshardcodesis_authenticated=True, and_PERSISTED_CLAIM_KEYS(line 19) does not persistauthentication_type— so an identity stored from an anonymous turn comes back authenticated and unrecoverable, routing down the MSAL path regardless of Bug 1.Repro
Anonymous-mode agent; store the reference on message, continue two seconds later:
Connect the Playground, send any message: the inbound reply works, the continuation raises the traceback above.
app.proactive.continue_conversation(storage round-trip) fails identically via Bug 2.Suggested fix
In
process_proactive, computeuse_anonymousfrom the claims identity exactly asprocess_activitydoes and pass it to both factory calls. For Bug 2, persistauthentication_type(or anis_authenticatedmarker) inclaims_from_identityand honor it inidentity_from_claims.Related: PR #508 (closed, unmerged) addressed the inbound anonymous
ClaimsIdentitybut not the proactive path.Workaround (verified against the Playground)
Anonymous continuation then works; authenticated identities keep the stock path. (The claims identity is kept in process memory rather than via
app.proactivestorage, sidestepping Bug 2.)