Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
80324cb
feat: Add `session_limit` parameter to control conversation history r…
danielquintas8 Dec 16, 2025
1f08ea4
refactor: linting
danielquintas8 Dec 16, 2025
82054fa
feat: Introduce `SessionSettings` to standardize session configuratio…
danielquintas8 Dec 17, 2025
ed73dda
feat: Integrate SessionSettings into Session and RunConfig for enhanc…
danielquintas8 Dec 17, 2025
1571292
feat: Enhance SQLiteSession to utilize SessionSettings for item retri…
danielquintas8 Dec 17, 2025
142e86a
feat: Enhance AdvancedSQLiteSession to utilize SessionSettings for it…
danielquintas8 Dec 17, 2025
55f6172
feat: Enhance DaprSession to utilize SessionSettings for item retriev…
danielquintas8 Dec 17, 2025
53a8ae6
feat: Enhance EncryptedSession to utilize SessionSettings for item re…
danielquintas8 Dec 17, 2025
1da6e62
feat: Enhance SQAlchemySession to utilize SessionSettings for item re…
danielquintas8 Dec 17, 2025
1216ab9
feat: Enhance Redis to utilize SessionSettings for item retrieval lim…
danielquintas8 Dec 17, 2025
44bf3e3
feat: Enhance OpenAIConversationsSession to utilize SessionSettings f…
danielquintas8 Dec 17, 2025
46e9a39
test: add key_prefix to RedisSession.from_url initialization in tests.
danielquintas8 Dec 17, 2025
dd3a367
feat: add `session_settings` parameter to `AdvancedSQLiteSession`
danielquintas8 Dec 17, 2025
7475ddf
feat: Add `session_id` property to `OpenAIConversationsSession` to ex…
danielquintas8 Dec 17, 2025
99a7226
refactor: Migrate session limit parameter in tests to use RunConfig a…
danielquintas8 Dec 17, 2025
c2c3090
fix: mypy issues
danielquintas8 Dec 17, 2025
6425a24
fix: lint and format
danielquintas8 Dec 17, 2025
9a8fa05
fix: add future annotations to SessionSettings for Python 3.9 compati…
danielquintas8 Dec 18, 2025
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
2 changes: 2 additions & 0 deletions src/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
OpenAIConversationsSession,
Session,
SessionABC,
SessionSettings,
SQLiteSession,
)
from .model_settings import ModelSettings
Expand Down Expand Up @@ -286,6 +287,7 @@ def enable_verbose_stdout_logging():
"AgentHooks",
"Session",
"SessionABC",
"SessionSettings",
"SQLiteSession",
"OpenAIConversationsSession",
"RunContextWrapper",
Expand Down
26 changes: 18 additions & 8 deletions src/agents/extensions/memory/advanced_sqlite_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from ...items import TResponseInputItem
from ...memory import SQLiteSession
from ...memory.session_settings import SessionSettings


class AdvancedSQLiteSession(SQLiteSession):
Expand All @@ -25,6 +26,7 @@ def __init__(
db_path: str | Path = ":memory:",
create_tables: bool = False,
logger: logging.Logger | None = None,
session_settings: SessionSettings | None = None,
**kwargs,
):
"""Initialize the AdvancedSQLiteSession.
Expand All @@ -36,7 +38,12 @@ def __init__(
logger: The logger to use. Defaults to the module logger
**kwargs: Additional keyword arguments to pass to the superclass
""" # noqa: E501
super().__init__(session_id, db_path, **kwargs)
super().__init__(
session_id=session_id,
db_path=db_path,
session_settings=session_settings,
**kwargs,
)
if create_tables:
self._init_structure_tables()
self._current_branch_id = "main"
Expand Down Expand Up @@ -132,12 +139,15 @@ async def get_items(
"""Get items from current or specified branch.

Args:
limit: Maximum number of items to return. If None, returns all items.
limit: Maximum number of items to return. If None, uses session_settings.limit.
branch_id: Branch to get items from. If None, uses current branch.

Returns:
List of conversation items from the specified branch.
"""
# Use session settings limit if no explicit limit provided
session_limit = limit if limit is not None else self.session_settings.limit

if branch_id is None:
branch_id = self._current_branch_id

Expand All @@ -148,7 +158,7 @@ def _get_all_items_sync():
# TODO: Refactor SQLiteSession to use asyncio.Lock instead of threading.Lock and update this code # noqa: E501
with self._lock if self._is_memory_db else threading.Lock():
with closing(conn.cursor()) as cursor:
if limit is None:
if session_limit is None:
cursor.execute(
"""
SELECT m.message_data
Expand All @@ -169,11 +179,11 @@ def _get_all_items_sync():
ORDER BY s.sequence_number DESC
LIMIT ?
""",
(self.session_id, branch_id, limit),
(self.session_id, branch_id, session_limit),
)

rows = cursor.fetchall()
if limit is not None:
if session_limit is not None:
rows = list(reversed(rows))

items = []
Expand All @@ -194,7 +204,7 @@ def _get_items_sync():
with self._lock if self._is_memory_db else threading.Lock():
with closing(conn.cursor()) as cursor:
# Get message IDs in correct order for this branch
if limit is None:
if session_limit is None:
cursor.execute(
"""
SELECT m.message_data
Expand All @@ -215,11 +225,11 @@ def _get_items_sync():
ORDER BY s.sequence_number DESC
LIMIT ?
""",
(self.session_id, branch_id, limit),
(self.session_id, branch_id, session_limit),
)

rows = cursor.fetchall()
if limit is not None:
if session_limit is not None:
rows = list(reversed(rows))

items = []
Expand Down
25 changes: 20 additions & 5 deletions src/agents/extensions/memory/dapr_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from ...items import TResponseInputItem
from ...logger import logger
from ...memory.session import SessionABC
from ...memory.session_settings import SessionSettings

# Type alias for consistency levels
ConsistencyLevel = Literal["eventual", "strong"]
Expand All @@ -64,6 +65,7 @@ def __init__(
dapr_client: DaprClient,
ttl: int | None = None,
consistency: ConsistencyLevel = DAPR_CONSISTENCY_EVENTUAL,
session_settings: SessionSettings | None = None,
):
"""Initializes a new DaprSession.

Expand All @@ -77,8 +79,11 @@ def __init__(
consistency (ConsistencyLevel, optional): Consistency level for state operations.
Use DAPR_CONSISTENCY_EVENTUAL or DAPR_CONSISTENCY_STRONG constants.
Defaults to DAPR_CONSISTENCY_EVENTUAL.
session_settings (SessionSettings | None): Session configuration settings including
default limit for retrieving items. If None, uses default SessionSettings().
"""
self.session_id = session_id
self.session_settings = session_settings or SessionSettings()
self._dapr_client = dapr_client
self._state_store_name = state_store_name
self._ttl = ttl
Expand All @@ -97,6 +102,7 @@ def from_address(
*,
state_store_name: str,
dapr_address: str = "localhost:50001",
session_settings: SessionSettings | None = None,
**kwargs: Any,
) -> DaprSession:
"""Create a session from a Dapr sidecar address.
Expand All @@ -105,6 +111,8 @@ def from_address(
session_id (str): Conversation ID.
state_store_name (str): Name of the Dapr state store component.
dapr_address (str): Dapr sidecar gRPC address. Defaults to "localhost:50001".
session_settings (SessionSettings | None): Session configuration settings including
default limit for retrieving items. If None, uses default SessionSettings().
**kwargs: Additional keyword arguments forwarded to the main constructor
(e.g., ttl, consistency).

Expand All @@ -119,7 +127,11 @@ def from_address(
"""
dapr_client = DaprClient(address=dapr_address)
session = cls(
session_id, state_store_name=state_store_name, dapr_client=dapr_client, **kwargs
session_id,
state_store_name=state_store_name,
dapr_client=dapr_client,
session_settings=session_settings,
**kwargs,
)
session._owns_client = True # We created the client, so we own it
return session
Expand Down Expand Up @@ -222,12 +234,15 @@ async def get_items(self, limit: int | None = None) -> list[TResponseInputItem]:
"""Retrieve the conversation history for this session.

Args:
limit: Maximum number of items to retrieve. If None, retrieves all items.
limit: Maximum number of items to retrieve. If None, uses session_settings.limit.
When specified, returns the latest N items in chronological order.

Returns:
List of input items representing the conversation history
"""
# Use session settings limit if no explicit limit provided
session_limit = limit if limit is not None else self.session_settings.limit

async with self._lock:
# Get messages from state store with consistency level
response = await self._dapr_client.get_state(
Expand All @@ -239,10 +254,10 @@ async def get_items(self, limit: int | None = None) -> list[TResponseInputItem]:
messages = self._decode_messages(response.data)
if not messages:
return []
if limit is not None:
if limit <= 0:
if session_limit is not None:
if session_limit <= 0:
return []
messages = messages[-limit:]
messages = messages[-session_limit:]
items: list[TResponseInputItem] = []
for msg in messages:
try:
Expand Down
11 changes: 11 additions & 0 deletions src/agents/extensions/memory/encrypt_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

from ...items import TResponseInputItem
from ...memory.session import SessionABC
from ...memory.session_settings import SessionSettings


class EncryptedEnvelope(TypedDict):
Expand Down Expand Up @@ -135,6 +136,16 @@ def __init__(
def __getattr__(self, name):
return getattr(self.underlying_session, name)

@property
def session_settings(self) -> SessionSettings:
"""Get session settings from the underlying session."""
return self.underlying_session.session_settings

@session_settings.setter
def session_settings(self, value: SessionSettings) -> None:
"""Set session settings on the underlying session."""
self.underlying_session.session_settings = value

def _wrap(self, item: TResponseInputItem) -> EncryptedEnvelope:
if isinstance(item, dict):
payload = item
Expand Down
26 changes: 21 additions & 5 deletions src/agents/extensions/memory/redis_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

from ...items import TResponseInputItem
from ...memory.session import SessionABC
from ...memory.session_settings import SessionSettings


class RedisSession(SessionABC):
Expand All @@ -48,6 +49,7 @@ def __init__(
redis_client: Redis,
key_prefix: str = "agents:session",
ttl: int | None = None,
session_settings: SessionSettings | None = None,
):
"""Initializes a new RedisSession.

Expand All @@ -58,8 +60,11 @@ def __init__(
Defaults to "agents:session".
ttl (int | None, optional): Time-to-live in seconds for session data.
If None, data persists indefinitely. Defaults to None.
session_settings (SessionSettings | None): Session configuration settings including
default limit for retrieving items. If None, uses default SessionSettings().
"""
self.session_id = session_id
self.session_settings = session_settings or SessionSettings()
self._redis = redis_client
self._key_prefix = key_prefix
self._ttl = ttl
Expand All @@ -78,6 +83,7 @@ def from_url(
*,
url: str,
redis_kwargs: dict[str, Any] | None = None,
session_settings: SessionSettings | None = None,
**kwargs: Any,
) -> RedisSession:
"""Create a session from a Redis URL string.
Expand All @@ -87,6 +93,8 @@ def from_url(
url (str): Redis URL, e.g. "redis://localhost:6379/0" or "rediss://host:6380".
redis_kwargs (dict[str, Any] | None): Additional keyword arguments forwarded to
redis.asyncio.from_url.
session_settings (SessionSettings | None): Session configuration settings including
default limit for retrieving items. If None, uses default SessionSettings().
**kwargs: Additional keyword arguments forwarded to the main constructor
(e.g., key_prefix, ttl, etc.).

Expand All @@ -96,7 +104,12 @@ def from_url(
redis_kwargs = redis_kwargs or {}

redis_client = redis.from_url(url, **redis_kwargs)
session = cls(session_id, redis_client=redis_client, **kwargs)
session = cls(
session_id,
redis_client=redis_client,
session_settings=session_settings,
**kwargs,
)
session._owns_client = True # We created the client, so we own it
return session

Expand Down Expand Up @@ -129,22 +142,25 @@ async def get_items(self, limit: int | None = None) -> list[TResponseInputItem]:
"""Retrieve the conversation history for this session.

Args:
limit: Maximum number of items to retrieve. If None, retrieves all items.
limit: Maximum number of items to retrieve. If None, uses session_settings.limit.
When specified, returns the latest N items in chronological order.

Returns:
List of input items representing the conversation history
"""
# Use session settings limit if no explicit limit provided
session_limit = limit if limit is not None else self.session_settings.limit

async with self._lock:
if limit is None:
if session_limit is None:
# Get all messages in chronological order
raw_messages = await self._redis.lrange(self._messages_key, 0, -1) # type: ignore[misc] # Redis library returns Union[Awaitable[T], T] in async context
else:
if limit <= 0:
if session_limit <= 0:
return []
# Get the latest N messages (Redis list is ordered chronologically)
# Use negative indices to get from the end - Redis uses -N to -1 for last N items
raw_messages = await self._redis.lrange(self._messages_key, -limit, -1) # type: ignore[misc] # Redis library returns Union[Awaitable[T], T] in async context
raw_messages = await self._redis.lrange(self._messages_key, -session_limit, -1) # type: ignore[misc] # Redis library returns Union[Awaitable[T], T] in async context

items: list[TResponseInputItem] = []
for raw_msg in raw_messages:
Expand Down
Loading