Skip to content
Closed
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
3 changes: 3 additions & 0 deletions docs/ref/extensions/memory/dakera_session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `DakeraSession`

::: agents.extensions.memory.dakera_session.DakeraSession
32 changes: 32 additions & 0 deletions docs/sessions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ Use this table to pick a starting point before reading the detailed examples bel
| `SQLAlchemySession` | Production apps with existing databases | Works with SQLAlchemy-supported databases |
| `MongoDBSession` | Apps already using MongoDB or needing multi-process storage | Async pymongo; atomic sequence counter for ordering |
| `DaprSession` | Cloud-native deployments with Dapr sidecars | Supports multiple state stores plus TTL and consistency controls |
| `DakeraSession` | Self-hosted persistent memory across sessions and workers | Backed by a [Dakera](https://github.com/dakera-ai/dakera-deploy) server over REST |
| `OpenAIConversationsSession` | Server-managed storage in OpenAI | OpenAI Conversations API-backed history |
| `OpenAIResponsesCompactionSession` | Long conversations with automatic compaction | Wrapper around another session backend |
| `AdvancedSQLiteSession` | SQLite plus branching/analytics | Heavier feature set; see dedicated page |
Expand Down Expand Up @@ -449,6 +450,37 @@ Notes:
- Two collections are used and both names are configurable via `sessions_collection=` (default `agent_sessions`) and `messages_collection=` (default `agent_messages`). Indexes are created automatically on first use. Each message document carries a monotonically increasing `seq` counter that preserves ordering across concurrent writers and processes.
- Use `await session.ping()` to verify connectivity before your first run.

### Dakera sessions

Use `DakeraSession` to persist conversation history on a self-hosted [Dakera](https://github.com/dakera-ai/dakera-deploy) memory server, so history survives process restarts and can be shared across workers that point at the same server.

```bash
pip install openai-agents[dakera]
```

```python
from agents import Agent, Runner
from agents.extensions.memory import DakeraSession

agent = Agent(name="Assistant")

# from_url creates and owns the AsyncDakeraClient; close() releases it.
session = DakeraSession.from_url(
session_id="user-123",
base_url="http://localhost:3000",
api_key="dk-...",
)
result = await Runner.run(agent, "Hello", session=session)
print(result.final_output)
await session.close()
```

Notes:

- `from_url(...)` creates and owns the `AsyncDakeraClient` and closes it on `session.close()`. If your application already manages a client, construct `DakeraSession(session_id, client=...)` directly; in that case `session.close()` is a no-op and lifecycle stays with the caller.
- Each conversation is isolated in its own Dakera namespace derived from `session_id` (`"{key_prefix}:{session_id}"`, `key_prefix` defaults to `agents:session`). The same `session_id` always resolves to the same history, so `get_items`/`pop_item`/`clear_session` are restart-safe. Every item carries a monotonically increasing `seq` in its metadata that preserves ordering across writers.
- Run a local server with the `dakera-ai/dakera-deploy` docker-compose stack (Dakera server + MinIO); it listens on port 3000 by default.

### Advanced SQLite sessions

Enhanced SQLite sessions with conversation branching, usage analytics, and structured queries:
Expand Down
106 changes: 106 additions & 0 deletions examples/memory/dakera_session_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Example demonstrating Dakera-backed session memory.

[Dakera](https://github.com/dakera-ai/dakera-deploy) is a self-hosted memory
server for AI agents. This example uses ``DakeraSession`` to persist conversation
history on a Dakera server so an agent keeps context across multiple runs.

Run a local Dakera server with the ``dakera-ai/dakera-deploy`` docker-compose
stack (Dakera server + MinIO); it listens on http://localhost:3000 by default.
Set ``DAKERA_BASE_URL`` and ``DAKERA_API_KEY`` to point at your own server.

pip install "openai-agents[dakera]"
"""

import asyncio
import os

from agents import Agent, Runner
from agents.extensions.memory import DakeraSession

DEFAULT_BASE_URL = "http://localhost:3000"


async def main() -> None:
agent = Agent(
name="Assistant",
instructions="Reply very concisely.",
)

base_url = os.environ.get("DAKERA_BASE_URL", DEFAULT_BASE_URL)
api_key = os.environ.get("DAKERA_API_KEY")

print("=== Dakera Session Example ===")
print(f"This example uses a Dakera server at {base_url}")
print("Set DAKERA_BASE_URL / DAKERA_API_KEY to use a different server.\n")

# `from_url` creates and owns the AsyncDakeraClient; `close()` releases it.
session = DakeraSession.from_url(
session_id="dakera_conversation_123",
base_url=base_url,
api_key=api_key,
)

try:
# Clear any existing history for a clean demonstration.
await session.clear_session()
print("Session cleared for clean demonstration.")
print("The agent will remember previous messages automatically.\n")

print("First turn:")
print("User: What city is the Golden Gate Bridge in?")
result = await Runner.run(
agent,
"What city is the Golden Gate Bridge in?",
session=session,
)
print(f"Assistant: {result.final_output}\n")

print("Second turn:")
print("User: What state is it in?")
result = await Runner.run(agent, "What state is it in?", session=session)
print(f"Assistant: {result.final_output}\n")

print("Third turn:")
print("User: What's the population of that state?")
result = await Runner.run(
agent,
"What's the population of that state?",
session=session,
)
print(f"Assistant: {result.final_output}\n")

print("=== Conversation Complete ===")
all_items = await session.get_items()
print(f"Total items stored in Dakera: {len(all_items)}")

# Demonstrate the limit parameter.
latest_items = await session.get_items(limit=2)
print(f"Latest {len(latest_items)} items retrieved via the limit parameter.")

# Demonstrate session isolation with a second conversation.
other = DakeraSession.from_url(
session_id="different_conversation_456",
base_url=base_url,
api_key=api_key,
)
try:
await other.clear_session()
await Runner.run(agent, "Hello, this is a new conversation!", session=other)
print(
"\nSession isolation: "
f"original={len(await session.get_items())} items, "
f"new={len(await other.get_items())} items"
)
finally:
await other.close()

except Exception as e: # pragma: no cover - example error handling
print(f"Error: {e}")
print(f"Make sure a Dakera server is running and reachable at {base_url}.")
print("See https://github.com/dakera-ai/dakera-deploy for a docker-compose setup.")
finally:
await session.close()


if __name__ == "__main__":
asyncio.run(main())
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ plugins:
- Async SQLite session: ref/extensions/memory/async_sqlite_session.md
- RedisSession: ref/extensions/memory/redis_session.md
- MongoDBSession: ref/extensions/memory/mongodb_session.md
- DakeraSession: ref/extensions/memory/dakera_session.md
- DaprSession: ref/extensions/memory/dapr_session.md
- EncryptedSession: ref/extensions/memory/encrypt_session.md
- AdvancedSQLiteSession: ref/extensions/memory/advanced_sqlite_session.md
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ encrypt = ["cryptography>=45.0, <46"]
redis = ["redis>=7"]
dapr = ["dapr>=1.16.0", "grpcio>=1.60.0"]
mongodb = ["pymongo>=4.14"]
dakera = ["dakera>=0.12.6"]
docker = ["docker>=6.1"]
blaxel = ["blaxel>=0.2.50", "aiohttp>=3.12,<4"]
daytona = ["daytona>=0.155.0"]
Expand Down Expand Up @@ -91,6 +92,7 @@ dev = [
"testcontainers==4.12.0", # pinned to 4.12.0 because 4.13.0 has a warning bug in wait_for_logs, see https://github.com/testcontainers/testcontainers-python/issues/874
"pyright==1.1.408",
"pymongo>=4.14",
"dakera>=0.12.6",
]

[tool.uv.workspace]
Expand Down
3 changes: 3 additions & 0 deletions src/agents/extensions/memory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
if TYPE_CHECKING:
from .advanced_sqlite_session import AdvancedSQLiteSession
from .async_sqlite_session import AsyncSQLiteSession
from .dakera_session import DakeraSession
from .dapr_session import (
DAPR_CONSISTENCY_EVENTUAL,
DAPR_CONSISTENCY_STRONG,
Expand All @@ -29,6 +30,7 @@
__all__: list[str] = [
"AdvancedSQLiteSession",
"AsyncSQLiteSession",
"DakeraSession",
"DAPR_CONSISTENCY_EVENTUAL",
"DAPR_CONSISTENCY_STRONG",
"DaprSession",
Expand All @@ -44,6 +46,7 @@
"SQLAlchemySession": (".sqlalchemy_session", ("sqlalchemy", "sqlalchemy")),
"AdvancedSQLiteSession": (".advanced_sqlite_session", None),
"AsyncSQLiteSession": (".async_sqlite_session", None),
"DakeraSession": (".dakera_session", ("dakera", "dakera")),
"DaprSession": (".dapr_session", ("dapr", "dapr")),
"DAPR_CONSISTENCY_EVENTUAL": (".dapr_session", ("dapr", "dapr")),
"DAPR_CONSISTENCY_STRONG": (".dapr_session", ("dapr", "dapr")),
Expand Down
Loading