Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9a88483
docs: add EXPLAIN to API reference documentation
cofin Dec 26, 2025
2c7ee8a
feat: Enhance ADK migration to include memory tables and improve sign…
cofin Dec 28, 2025
60c8059
chore(refactor): configuration handling and type guards across modules
cofin Dec 28, 2025
245d0ba
feat: Introduce _ADKMemoryStoreConfig for improved memory store confi…
cofin Dec 28, 2025
383df5e
feat(protocols): add new protocols for enhanced functionality
cofin Dec 28, 2025
aea1876
chore: remove `hasattr` usages
cofin Dec 28, 2025
8804203
fix: test corrections
cofin Dec 29, 2025
256a2f0
refactor(tests): remove xfail markers for DuckDB and PostgreSQL edge …
cofin Dec 29, 2025
16a0d7e
fix: remove type ignore comments for fetchall calls in memory store
cofin Dec 29, 2025
f61c213
refactor: clean up code and improve type hints across multiple files
cofin Dec 29, 2025
96eee33
refactor: clean up imports and add type hints in various modules
cofin Dec 29, 2025
93f488c
refactor: remove unused __slots__ definition from BaseTypeConverter
cofin Dec 29, 2025
184f58a
refactor: improve type guard checks and enhance memory efficiency tests
cofin Dec 29, 2025
21ff143
refactor: enhance error handling in statement execution and improve t…
cofin Dec 29, 2025
d721ce5
chore: compilation progress
cofin Dec 29, 2025
bc4d658
fix: consolidation
cofin Dec 29, 2025
b82b692
refactor: enhance adapter modules and implement pipeline driver proto…
cofin Dec 29, 2025
558eb0e
feat: cleanup type handling
cofin Dec 30, 2025
e2fd781
feat: cleanup
cofin Dec 30, 2025
ea43628
refactor: streamline type imports and annotations in type_converter.py
cofin Dec 30, 2025
7738e5f
Refactor type imports across adapters to use _typing module
cofin Dec 30, 2025
4910def
feat: enhance SQLite connection pool configuration by adding optimiza…
cofin Dec 30, 2025
cf173bb
feat: enhance test configurations and skip logic for mypyc-compiled m…
cofin Dec 30, 2025
ddb7455
Refactor code structure for improved readability and maintainability
cofin Jan 4, 2026
0c9c880
Refactor arrow_helpers to support RecordBatchReader return format
cofin Jan 5, 2026
a17ac96
feat: implement AsyncArrowBatchIterator for async streaming of Arrow …
cofin Jan 5, 2026
e3ea1e0
chore: linting
cofin Jan 5, 2026
37a2ccc
refactor: update driver classes to use __slots__ for memory optimization
cofin Jan 5, 2026
ee01c1d
chore: clean up pyproject.toml include/exclude patterns for better cl…
cofin Jan 5, 2026
5dc7131
feat: enhance AsyncpgConfig to support AlloyDB instance validation an…
cofin Jan 5, 2026
ca26df3
chore: update google-auth to version 2.46.0 and pytest-databases to v…
cofin Jan 5, 2026
00639be
feat: add connection_config to mock configurations for protocol valid…
cofin Jan 6, 2026
c409e54
fix: use NonCallableMock in config resolver tests
cofin Jan 6, 2026
8814c5c
style: apply ruff formatting to test_config_resolver
cofin Jan 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ setup.py
tmp/
*.log
.tmp
.tmp_mypyc
TODO*
.env
tools/*.json
Expand Down
14 changes: 13 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ SQLSpec is a type-safe SQL query mapper designed for minimal abstraction between
2. **Adapters (`sqlspec/adapters/`)**: Database-specific implementations. Each adapter consists of:
- `config.py`: Configuration classes specific to the database
- `driver.py`: Driver implementation (sync/async) that executes queries
- `_types.py`: Type definitions specific to the adapter or other uncompilable mypyc objects
- `_typing.py`: Type definitions specific to the adapter or other uncompilable mypyc objects
- Supported adapters: `adbc`, `aiosqlite`, `asyncmy`, `asyncpg`, `bigquery`, `duckdb`, `oracledb`, `psqlpy`, `psycopg`, `sqlite`

3. **Driver System (`sqlspec/driver/`)**: Base classes and mixins for all database drivers:
Expand Down Expand Up @@ -155,6 +155,15 @@ class MyAdapterDriver(SyncDriverBase):
- Add integration tests under `tests/integration/test_adapters/<adapter>/test_driver.py::test_*statement_stack*` that cover native path, sequential fallback, and continue-on-error.
- Guard base behavior (empty stacks, large stacks, transaction boundaries) via `tests/integration/test_stack_edge_cases.py`.

### ADK Memory Store Pattern

- `SQLSpecMemoryService` delegates storage to adapter-backed memory stores (`BaseAsyncADKMemoryStore` / `BaseSyncADKMemoryStore`).
- All ADK settings live in `extension_config["adk"]`; memory flags are `enable_memory`, `include_memory_migration`, `memory_table`, `memory_use_fts`, and `memory_max_results`.
- Search strategy is driver-determined: `memory_use_fts=True` enables adapter FTS when available, otherwise fall back to `LIKE`/`ILIKE` with warning on failure.
- Deduplication is keyed by `event_id` with idempotent inserts (ignore duplicates, return inserted count).
- Multi-tenancy uses the shared `owner_id_column` DDL; stores parse the column name to bind filter parameters.
- TTL cleanup is explicit via store helpers or CLI (`delete_entries_older_than`, `sqlspec adk memory cleanup`).

### Driver Parameter Profile Registry

- All adapter parameter defaults live in `DriverParameterProfile` entries inside `sqlspec/core/parameters.py`.
Expand Down Expand Up @@ -340,6 +349,9 @@ Prohibited: test coverage tables, file change lists, quality metrics, commit bre
| Type Handler | `docs/guides/development/implementation-patterns.md#type-handler-pattern` |
| Framework Extension | `docs/guides/development/implementation-patterns.md#framework-extension-pattern` |
| EXPLAIN Builder | `docs/guides/development/implementation-patterns.md#explain-builder-pattern` |
| Dynamic Optional Deps | `docs/guides/development/implementation-patterns.md#dynamic-optional-dependency-pattern` |
| Eager Compilation | `docs/guides/development/implementation-patterns.md#eager-compilation-pattern` |
| Protocol Capability | `docs/guides/development/implementation-patterns.md#protocol-capability-property-pattern` |
| Custom SQLGlot Dialect | `docs/guides/architecture/custom-sqlglot-dialects.md#custom-sqlglot-dialect` |
| Events Extension | `docs/guides/events/database-event-channels.md#events-architecture` |
| Binary Data Encoding | `sqlspec/adapters/spanner/_type_handlers.py` |
Expand Down
15 changes: 15 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ SQLSpec Changelog
Recent Updates
==============

ADK Memory Store
----------------

- Added ``SQLSpecMemoryService`` and ``SQLSpecSyncMemoryService`` for SQLSpec-backed ADK memory storage.
- Implemented adapter-specific memory stores with optional full-text search (`memory_use_fts`) and simple fallback search.
- Extended ADK migrations to include memory tables with configurable ``include_memory_migration`` toggles.
- Added CLI commands for memory cleanup and verification (`sqlspec adk memory cleanup/verify`).

Driver Layer Compilation
------------------------

- Compiled driver base classes and mixins with mypyc to reduce dispatch overhead in the execution pipeline.
- Replaced dynamic ``getattr`` patterns with protocol-driven access for mypyc compatibility.
- Added driver protocols and updated mypyc build configuration to include driver modules.

Database Event Channels
-----------------------

Expand Down
19 changes: 17 additions & 2 deletions docs/examples/extensions/adk/litestar_aiosqlite.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Expose SQLSpec-backed ADK sessions through a Litestar endpoint."""
"""Expose SQLSpec-backed ADK sessions and memory through Litestar endpoints."""

import asyncio
from typing import Any
Expand All @@ -7,18 +7,25 @@

from sqlspec.adapters.aiosqlite import AiosqliteConfig
from sqlspec.adapters.aiosqlite.adk import AiosqliteADKStore
from sqlspec.adapters.aiosqlite.adk.memory_store import AiosqliteADKMemoryStore
from sqlspec.extensions.adk import SQLSpecSessionService
from sqlspec.extensions.adk.memory import SQLSpecMemoryService

config = AiosqliteConfig(connection_config={"database": ":memory:"})
service: "SQLSpecSessionService | None" = None
memory_service: "SQLSpecMemoryService | None" = None


async def startup() -> None:
"""Initialize the ADK store when the app boots."""
global service
global memory_service
store = AiosqliteADKStore(config)
memory_store = AiosqliteADKMemoryStore(config)
await store.create_tables()
await memory_store.create_tables()
service = SQLSpecSessionService(store)
memory_service = SQLSpecMemoryService(memory_store)


@get("/sessions")
Expand All @@ -29,7 +36,15 @@ async def list_sessions() -> "dict[str, Any]":
return {"count": len(sessions.sessions)}


app = Litestar(route_handlers=[list_sessions], on_startup=[startup])
@get("/memories")
async def list_memories(query: str = "demo") -> "dict[str, Any]":
"""Return memory count for a query string."""
assert memory_service is not None
response = await memory_service.search_memory(app_name="docs", user_id="demo", query=query)
return {"count": len(response.memories)}


app = Litestar(route_handlers=[list_sessions, list_memories], on_startup=[startup])


def main() -> None:
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/extensions/adk/litestar_aiosqlite.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
ADK + Litestar Endpoint
=======================

Initialize ``SQLSpecSessionService`` inside Litestar and expose a ``/sessions`` endpoint backed by
AioSQLite.
Initialize ``SQLSpecSessionService`` and ``SQLSpecMemoryService`` inside Litestar and expose
``/sessions`` plus ``/memories`` endpoints backed by AioSQLite.

.. code-block:: console

Expand Down
53 changes: 53 additions & 0 deletions docs/examples/extensions/adk/runner_memory_aiosqlite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Run an ADK agent with SQLSpec-backed session and memory services (AioSQLite)."""

import asyncio

from google.adk.agents.llm_agent import LlmAgent
from google.adk.apps.app import App
from google.adk.runners import Runner
from google.genai import types

from sqlspec.adapters.aiosqlite import AiosqliteConfig
from sqlspec.adapters.aiosqlite.adk import AiosqliteADKStore
from sqlspec.adapters.aiosqlite.adk.memory_store import AiosqliteADKMemoryStore
from sqlspec.extensions.adk import SQLSpecSessionService
from sqlspec.extensions.adk.memory import SQLSpecMemoryService

__all__ = ("main",)


async def main() -> None:
"""Run a single ADK turn, then persist memory and search it."""
config = AiosqliteConfig(
connection_config={"database": ":memory:"}, extension_config={"adk": {"memory_use_fts": False}}
)
session_store = AiosqliteADKStore(config)
memory_store = AiosqliteADKMemoryStore(config)
await session_store.create_tables()
await memory_store.create_tables()

session_service = SQLSpecSessionService(session_store)
memory_service = SQLSpecMemoryService(memory_store)

agent = LlmAgent(name="sqlspec_agent", model="gemini-2.5-flash", instruction="Answer briefly.")
app = App(name="sqlspec_demo", root_agent=agent)
runner = Runner(app=app, session_service=session_service, memory_service=memory_service)

session_id = "session-1"
user_id = "demo-user"
await session_service.create_session(app_name=app.name, user_id=user_id, session_id=session_id)

new_message = types.UserContent(parts=[types.Part(text="Remember I like espresso.")])
async for _event in runner.run_async(user_id=user_id, session_id=session_id, new_message=new_message):
pass

session = await session_service.get_session(app_name=app.name, user_id=user_id, session_id=session_id)
if session:
await memory_service.add_session_to_memory(session)

response = await memory_service.search_memory(app_name=app.name, user_id=user_id, query="espresso")
print({"memories": len(response.memories)})


if __name__ == "__main__":
asyncio.run(main())
19 changes: 19 additions & 0 deletions docs/examples/extensions/adk/runner_memory_aiosqlite.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
ADK Runner with Memory (AioSQLite)
==================================

Run an ADK ``Runner`` with SQLSpec-backed session and memory services, then
persist memories from the completed session.

This example requires Google ADK credentials (for example, a configured API key)
and network access to the model provider.

.. code-block:: console

uv run python docs/examples/extensions/adk/runner_memory_aiosqlite.py

Source
------

.. literalinclude:: runner_memory_aiosqlite.py
:language: python
:linenos:
3 changes: 3 additions & 0 deletions docs/examples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ Extensions
- Create an ADK session, append events, and fetch the transcript using SQLSpec’s AioSQLite store.
* - ``extensions/adk/litestar_aiosqlite.py``
- Wire ``SQLSpecSessionService`` into Litestar and expose a simple ``/sessions`` endpoint.
* - ``extensions/adk/runner_memory_aiosqlite.py``
- Run an ADK ``Runner`` with SQLSpec-backed session + memory services, then query stored memories.

Shared Utilities
----------------
Expand All @@ -150,6 +152,7 @@ Shared Utilities
loaders/sql_files
extensions/adk/basic_aiosqlite
extensions/adk/litestar_aiosqlite
extensions/adk/runner_memory_aiosqlite
frameworks/fastapi/aiosqlite_app
frameworks/fastapi/sqlite_app
frameworks/starlette/aiosqlite_app
Expand Down
86 changes: 84 additions & 2 deletions docs/extensions/adk/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,54 @@ SQLSpecSessionService
:doc:`/examples/extensions/adk/litestar_aiosqlite`
Web framework integration using Litestar

Base Store Classes
==================
Memory Service
==============

SQLSpecMemoryService
--------------------

.. autoclass:: sqlspec.extensions.adk.memory.SQLSpecMemoryService
:show-inheritance:

SQLSpec-backed implementation of Google ADK's ``BaseMemoryService``.

This service persists memories extracted from completed sessions and exposes
search capabilities via adapter-specific stores.

**Attributes:**

.. attribute:: store
:no-index:

The database store implementation (e.g., ``AsyncpgADKMemoryStore``).

**Example:**

.. code-block:: python

from sqlspec.adapters.asyncpg.adk.memory_store import AsyncpgADKMemoryStore
from sqlspec.extensions.adk.memory import SQLSpecMemoryService

store = AsyncpgADKMemoryStore(config)
await store.create_tables()
memory_service = SQLSpecMemoryService(store)

.. seealso::

:doc:`/examples/extensions/adk/runner_memory_aiosqlite`
ADK Runner example with SQLSpec-backed memory service

SQLSpecSyncMemoryService
------------------------

.. autoclass:: sqlspec.extensions.adk.memory.SQLSpecSyncMemoryService
:show-inheritance:

Sync memory service for sync adapters (SQLite/DuckDB). This class does not
inherit from ADK's async ``BaseMemoryService`` but mirrors the async API.

Session Store Base Classes
==========================

BaseAsyncADKStore
------------
Expand Down Expand Up @@ -170,6 +216,35 @@ BaseSyncADKStore
store = SqliteADKStore(config)
store.create_tables()

Memory Store Base Classes
=========================

BaseAsyncADKMemoryStore
-----------------------

.. autoclass:: sqlspec.extensions.adk.memory.BaseAsyncADKMemoryStore
:show-inheritance:

Abstract base class for async SQLSpec-backed ADK memory stores.

**Abstract Methods:**

- :meth:`create_tables`
- :meth:`insert_memory_entries`
- :meth:`search_entries`
- :meth:`delete_entries_by_session`
- :meth:`delete_entries_older_than`
- :meth:`_get_create_memory_table_sql`
- :meth:`_get_drop_memory_table_sql`

BaseSyncADKMemoryStore
----------------------

.. autoclass:: sqlspec.extensions.adk.memory.BaseSyncADKMemoryStore
:show-inheritance:

Abstract base class for sync SQLSpec-backed ADK memory stores.

Type Definitions
================

Expand Down Expand Up @@ -218,6 +293,13 @@ SessionRecord

from datetime import datetime, timezone

MemoryRecord
------------

.. autoclass:: sqlspec.extensions.adk.memory._types.MemoryRecord

TypedDict representing a memory database record.

record: SessionRecord = {
"id": "550e8400-e29b-41d4-a716-446655440000",
"app_name": "weather_agent",
Expand Down
39 changes: 23 additions & 16 deletions docs/extensions/adk/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ Google ADK Extension
migrations
schema

Session and event storage for the Google Agent Development Kit (ADK) using SQLSpec database adapters.
Session, event, and memory storage for the Google Agent Development Kit (ADK) using SQLSpec database adapters.

Overview
========

The SQLSpec ADK extension provides persistent storage for `Google Agent Development Kit <https://github.com/google/genai>`_ sessions and events, enabling stateful AI agent applications with database-backed conversation history.
The SQLSpec ADK extension provides persistent storage for `Google Agent Development Kit <https://github.com/google/genai>`_ sessions, events, and long-term memory entries, enabling stateful AI agent applications with database-backed conversation history and recall.

This extension implements ADK's ``BaseSessionService`` protocol, allowing AI agents to store and retrieve:

- **Session State**: Persistent conversation context and application state
- **Event History**: Complete record of user/assistant interactions
- **Long-term Memory**: Searchable memory entries extracted from completed sessions
- **Multi-User Support**: Isolated sessions per application and user
- **Type-Safe Storage**: Full type safety with TypedDicts and validated records

Expand Down Expand Up @@ -149,25 +150,30 @@ The extension follows a layered architecture:
└──────────┬──────────┘
┌──────────▼──────────┐
│ SQLSpecSessionService│ ← Implements BaseSessionService
└──────────┬──────────┘
┌──────────▼──────────┐
│ Store Implementation│ ← AsyncpgADKStore, SqliteADKStore, etc.
└──────────┬──────────┘
┌──────────▼──────────┐
│ SQLSpec Config │ ← AsyncpgConfig, SqliteConfig, etc.
┌─────────────────────┐
│ ADK Runner │
└──────────┬──────────┘
┌──────────▼──────────┐
│ Database │
└─────────────────────┘
┌──────────▼──────────┐ ┌────────────────────┐
│ SQLSpecSessionService│ │ SQLSpecMemoryService│
└──────────┬──────────┘ └──────────┬─────────┘
│ │
┌──────────▼──────────┐ ┌─────────▼─────────┐
│ Session Store │ │ Memory Store │
└──────────┬──────────┘ └─────────┬─────────┘
│ │
┌──────────▼──────────┐ ┌─────────▼─────────┐
│ SQLSpec Config │ │ SQLSpec Config │
└──────────┬──────────┘ └─────────┬─────────┘
│ │
┌──────────▼──────────┐ ┌─────────▼─────────┐
│ Database │ │ Database │
└─────────────────────┘ └───────────────────┘

**Layers:**

1. **Service Layer** (``SQLSpecSessionService``): Implements ADK's ``BaseSessionService`` protocol
2. **Store Layer** (``BaseAsyncADKStore``): Abstract database operations for each adapter
1. **Service Layer** (``SQLSpecSessionService`` / ``SQLSpecMemoryService``): Implements ADK service protocols
2. **Store Layer** (``BaseAsyncADKStore`` / ``BaseAsyncADKMemoryStore``): Abstract database operations per adapter
3. **Config Layer** (SQLSpec): Connection pooling and resource management
4. **Database Layer**: Physical storage with database-specific optimizations

Expand All @@ -178,6 +184,7 @@ New curated examples live in the :doc:`examples catalog </examples/index>`:

* :doc:`/examples/extensions/adk/basic_aiosqlite` – create a session, append two events, and read the transcript using AioSQLite storage.
* :doc:`/examples/extensions/adk/litestar_aiosqlite` – initialize ``SQLSpecSessionService`` inside a Litestar app and expose a ``/sessions`` route.
* :doc:`/examples/extensions/adk/runner_memory_aiosqlite` – run an ADK ``Runner`` with SQLSpec-backed memory and search stored memories.

Use Cases
=========
Expand Down
Loading