Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions python/packages/core/agent_framework/_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -1197,11 +1197,16 @@ async def _prepare_run_context(
# Resolve conversation_id from the same combined view so an agent-level default is honored
# when the runtime omits it (a live session id still takes precedence below).
effective_conversation_id = effective_options.get("conversation_id")
# Auto-inject InMemoryHistoryProvider when session is provided, no context providers
# registered, and no service-side storage indicators
# Auto-inject InMemoryHistoryProvider when a session is provided and no
# loading history provider exists yet. Gating on history providers (not
# any context provider) keeps local history when non-history providers
# like SkillsProvider are present, so multi-turn flows on stateless
# clients don't lose prior messages.
if (
session is not None
and not self.context_providers
and not any(
provider.load_messages for provider in self.context_providers if isinstance(provider, HistoryProvider)
)
and not session.service_session_id
and not effective_conversation_id
and not service_stores_history
Expand Down
57 changes: 57 additions & 0 deletions python/packages/core/tests/core/test_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -2534,6 +2534,63 @@ async def test_stores_by_default_with_store_false_in_default_options_injects_inm
assert any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers)


async def test_non_history_context_provider_still_injects_inmemory(
client: SupportsChatGetResponse,
) -> None:
"""A non-history context provider must not suppress local history injection.

Regression for the case where registering a context provider that is not a
HistoryProvider (e.g. SkillsProvider, FileAccessProvider, or a RAG memory
provider) prevented the auto-injected InMemoryHistoryProvider. Without local
history, multi-turn flows on stateless clients (such as the tool-approval
resume turn) drop the prior assistant function_call.
"""
from agent_framework._sessions import InMemoryHistoryProvider

agent = Agent(client=client, context_providers=[MockContextProvider()])
session = agent.create_session()

await agent.run("Hello", session=session)

# The non-history provider should not block local-history injection.
assert any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers)


async def test_existing_loading_history_provider_skips_inmemory_injection(
client: SupportsChatGetResponse,
) -> None:
"""An existing loading HistoryProvider suppresses injection even with other providers."""
from agent_framework._sessions import InMemoryHistoryProvider

existing = InMemoryHistoryProvider("custom", load_messages=True)
agent = Agent(client=client, context_providers=[existing, MockContextProvider()])
session = agent.create_session()

await agent.run("Hello", session=session)

history_providers = [p for p in agent.context_providers if isinstance(p, InMemoryHistoryProvider)]
assert history_providers == [existing]


async def test_persist_only_history_provider_still_injects_inmemory(
client: SupportsChatGetResponse,
) -> None:
"""A persist-only (load_messages=False) HistoryProvider does not satisfy the loading need."""
from agent_framework._sessions import InMemoryHistoryProvider

audit = InMemoryHistoryProvider("audit", load_messages=False)
agent = Agent(client=client, context_providers=[audit])
session = agent.create_session()

await agent.run("Hello", session=session)

loading_providers = [
p for p in agent.context_providers if isinstance(p, InMemoryHistoryProvider) and p.load_messages
]
assert len(loading_providers) == 1
assert loading_providers[0] is not audit


async def test_shared_local_storage_cross_provider_responses_history_does_not_leak_fc_id() -> None:
"""Responses-specific replay metadata should stay local to Responses when session storage is shared."""
from openai.types.chat.chat_completion import ChatCompletion, Choice
Expand Down
Loading