Skip to main content

OpenAI Agents SDK SQLAlchemySession on MySQL: fixing 'VARCHAR requires a length on dialect mysql'

Point the Agents SDK's SQLAlchemy session at MySQL or MariaDB with create_tables=True and the first write fails before any table exists. Here's why, and a small schema script that works around it.

Updated 14 min readMarkdown

Disclosure: I work on MemorySync (I'm Mohammed, the founder). I drafted this article with AI assistance and checked it against the GitHub issue, the two related PRs, the OpenAI Agents SDK docs, the SDK source on PyPI and GitHub, the SQLAlchemy docs and the MemorySync docs listed under Sources on Oct 31, 2026. Code status: I ran everything offline with outbound connections blocked and no API keys, on Python 3.12.15 with openai-agents 0.22.0 (SQLAlchemy 2.0.52, the issue's versions) and 0.24.0 (SQLAlchemy 2.1.6, the latest release when I ran it on Oct 31). I compiled the SDK's own table definitions for MySQL, MariaDB, PostgreSQL and SQLite with SQLAlchemy's mock engine (no database server), compiled the Step 2 schema script for MySQL and MariaDB the same way, and ran the Step 2 script and the Step 3 session calls against a local SQLite file. I didn't run anything against a real MySQL or MariaDB server, a real model, Runner.run, or the MemorySync snippet.

You pointed the OpenAI Agents SDK's SQLAlchemySession at MySQL, passed create_tables=True, and the first add_items() (or the first Runner.run(..., session=session)) fails with:

sqlalchemy.exc.CompileError: (in table 'agent_sessions', column 'session_id'): VARCHAR requires a length on dialect mysql

With a mariadb+...:// URL, the last word is mariadb. That's openai-agents-python issue #4740, "SQLAlchemySession(create_tables=True) cannot create tables on MySQL due to unbounded String columns", reported on openai-agents 0.22.0 with MySQL 8.0.46.

Short version: the SDK declares both session_id columns as a String with no length, and SQLAlchemy won't compile that into a MySQL or MariaDB CREATE TABLE. There's no released fix. The fix PR was closed without merging. The workaround is to create the two tables yourself with a bounded VARCHAR (Step 2), then use create_tables=False (Step 3).

MemorySync doesn't fix this bug. It's in the SDK's SQLAlchemy session, and the fix below is plain SQLAlchemy. MemorySync comes up once, near the end, as a different place to keep session history.

Skip the session-table plumbing. The MemorySync OpenAI Agents SDK guide shows a hosted Session for Runner.run(..., session=...), so there's no session table to create. It doesn't fix this SQLAlchemySession bug. When you're ready, see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

Status (Nov 14, 2026)

  • #4740 was opened Aug 28, 2026 (17:09 IST) and closed as completed on Sep 26 (03:19 IST) with 3 comments (still closed when I re-checked on Nov 14). The maintainer's closing comment points to the docs PR #4743, which was merged and documents which async driver to install for each database (aiomysql for mysql+aiomysql:// URLs). It doesn't change the table definitions.
  • The code fix, #4741 "fix(sessions): support MySQL and MariaDB schema creation", was closed without merging on Sep 25 (23:37 IST). The maintainer's comment: "We won't continue working on this due to our current priorities but may revisit it in the future."
  • The latest release on PyPI is still openai-agents 0.24.0 (published Oct 15, 2026, 08:08 IST; re-checked Nov 14). Its sqlalchemy_session.py still declares Column("session_id", String, primary_key=True) and an unbounded String for the messages table's session_id, and so does the main branch (source read Nov 14, not a run).
  • The SQLAlchemy session docs still say the class works with "any database supported by SQLAlchemy (PostgreSQL, MySQL, SQLite, etc.)". The API reference describes create_tables as "Defaults to False for production use. Set to True for development and testing when migrations aren't used."

Step 1: Confirm it's this bug

You're looking at this bug if all of these are true:

  1. You use SQLAlchemySession (or SQLAlchemySession.from_url(...)) with a mysql+...:// or mariadb+...:// URL.
  2. You pass create_tables=True.
  3. The error is a CompileError naming agent_sessions (or your sessions_table= name), column session_id, and ends with VARCHAR requires a length on dialect mysql (or mariadb).

Per the issue, the connection succeeds and the error comes while SQLAlchemy compiles the first CREATE TABLE, "before any CREATE statement reaches MySQL. No tables are created."

MySQL and MariaDB need a length on every VARCHAR. PostgreSQL and SQLite don't, which is why the same code works there. Here's what I got compiling the SDK's own two tables for each database with SQLAlchemy's mock engine:

Database (dialect) openai-agents 0.22.0 + SQLAlchemy 2.0.52 openai-agents 0.24.0 + SQLAlchemy 2.1.6
MySQL CompileError: ... VARCHAR requires a length on dialect mysql same error
MariaDB CompileError: ... VARCHAR requires a length on dialect mariadb same error
PostgreSQL both tables and the index compile same
SQLite both tables and the index compile same

The error text matched the issue's exactly. The mock engine only compiles SQL, so this confirms the cause, not anything that happens on a live server.

If switching databases is an option, PostgreSQL (postgresql+asyncpg://, whose driver the sqlalchemy extra already installs, per the docs) or SQLite works with create_tables=True as is. If you need MySQL or MariaDB, keep going.

Step 2: Create the tables yourself

SQLAlchemySession only needs two tables with the right names and columns. The column types matter only when the tables are created: reading and writing just use the column names (Step 3). So you can create the tables once with MySQL-safe types and let the SDK use them.

Save this as create_agent_session_tables.py. It mirrors the SDK's two tables and index, but gives session_id a bounded type on MySQL and MariaDB using SQLAlchemy's with_variant(), the approach the issue suggested and PR #4741 used:

# create_agent_session_tables.py
"""Create the SQLAlchemySession tables with MySQL/MariaDB-safe column types.
Run it once per database, or copy the two tables into your migration tool."""
import asyncio
import os

from sqlalchemy import (
    TIMESTAMP, Column, ForeignKey, Index, Integer, MetaData, String, Table, Text, text,
)
from sqlalchemy.dialects import mysql
from sqlalchemy.ext.asyncio import create_async_engine

SESSIONS_TABLE = "agent_sessions"  # match sessions_table= if you override it
MESSAGES_TABLE = "agent_messages"  # match messages_table= if you override it

# Bounded, case-sensitive session IDs on MySQL/MariaDB; plain VARCHAR elsewhere.
SESSION_ID = String().with_variant(
    mysql.VARCHAR(190, charset="utf8mb4", collation="utf8mb4_bin"), "mysql", "mariadb"
)
# LONGTEXT on MySQL/MariaDB so large items aren't cut off at 64 KB.
MESSAGE_DATA = Text().with_variant(mysql.LONGTEXT(), "mysql", "mariadb")

metadata = MetaData()
Table(
    SESSIONS_TABLE,
    metadata,
    Column("session_id", SESSION_ID, primary_key=True),
    Column("created_at", TIMESTAMP(timezone=False),
           server_default=text("CURRENT_TIMESTAMP"), nullable=False),
    Column("updated_at", TIMESTAMP(timezone=False),
           server_default=text("CURRENT_TIMESTAMP"), nullable=False),
)
Table(
    MESSAGES_TABLE,
    metadata,
    Column("id", Integer, primary_key=True, autoincrement=True),
    Column("session_id", SESSION_ID,
           ForeignKey(f"{SESSIONS_TABLE}.session_id", ondelete="CASCADE"), nullable=False),
    Column("message_data", MESSAGE_DATA, nullable=False),
    Column("created_at", TIMESTAMP(timezone=False),
           server_default=text("CURRENT_TIMESTAMP"), nullable=False),
    Index(f"idx_{MESSAGES_TABLE}_session_time", "session_id", "created_at"),
    sqlite_autoincrement=True,
)


async def main() -> None:
    engine = create_async_engine(os.environ["DATABASE_URL"])
    async with engine.begin() as conn:
        await conn.run_sync(metadata.create_all)
    await engine.dispose()


if __name__ == "__main__":
    asyncio.run(main())

Run it once with DATABASE_URL set to the same mysql+aiomysql://... URL your app uses (install the driver first: pip install 'openai-agents[sqlalchemy]' 'aiomysql[rsa]', per the docs). If you use Alembic or another migration tool, copy the two tables into a migration instead.

For MySQL, this compiled (mock engine, both SQLAlchemy versions) to:

CREATE TABLE agent_sessions (
	session_id VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
	created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
	updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (session_id)
)
CREATE TABLE agent_messages (
	id INTEGER NOT NULL AUTO_INCREMENT,
	session_id VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
	message_data LONGTEXT NOT NULL,
	created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (id),
	FOREIGN KEY(session_id) REFERENCES agent_sessions (session_id) ON DELETE CASCADE
)
CREATE INDEX idx_agent_messages_session_time ON agent_messages (session_id, created_at)

MariaDB compiled to the same three statements. You can also run these statements by hand, or adapt them to your migrations.

Why these choices, and where each comes from:

  • VARCHAR(190). PR #4741 picked 190 so the (session_id, created_at) index "remains within the traditional 767-byte InnoDB key limit under utf8mb4" (190 characters × 4 bytes = 760). If your server allows longer index keys and your session IDs are longer, pick a bigger length. Either way, session IDs longer than the column won't fit, so keep yours short (a UUID is 36 characters).
  • CHARACTER SET utf8mb4 COLLATE utf8mb4_bin. In the PR #4741 review, a case-insensitive collation would make Foo and foo share one history. The PR switched to utf8mb4_bin and declared the character set with it, because, per the PR, a collation alone inherits the database's character set and the server rejects utf8mb4_bin on a non-utf8mb4 one. The same review noted that utf8mb4_bin still ignores trailing spaces when comparing, so don't build session IDs that differ only by a trailing space.
  • LONGTEXT for message_data. The SDK uses Text, which is MySQL's 64 KB TEXT. In PR #4910 (also closed without merging), the author's live test stored a 1,200,028-byte serialized item: strict mode rejected the insert with error 1406, non-strict mode cut it to 65,535 bytes of invalid JSON, and LONGTEXT kept it intact.

What I checked offline, on both SDK versions:

Check Result
Step 2 tables compile for MySQL and MariaDB (mock engine) yes, the statements above
Same table names, column names, primary keys, nullability, foreign key and index name as the SDK's own tables yes
The SDK's own INSERT / SELECT / DELETE against these tables compile for MySQL with the unbounded String columns yes (only CREATE TABLE needs a length)
Step 2 script run against a local SQLite file, then Step 3's session: add_items, get_items, pop_item, clear_session all worked; clear_session left another session's items alone

I didn't run the script against a real MySQL or MariaDB server, so I haven't seen server-side behavior (character set, index limits, strict mode) myself. The PR #4741 author reported live runs on MySQL 8.0.46, MySQL 5.7.44 and MariaDB 11.8.9 for the same column type, with the SDK's patched code rather than this script.

Step 3: Use the session with create_tables=False

Now point the SDK at the tables you created and turn off its table creation:

import os

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

agent = Agent(name="Assistant", instructions="Reply concisely.")

session = SQLAlchemySession.from_url(
    "user-123",
    url=os.environ["DATABASE_URL"],  # mysql+aiomysql://...
    create_tables=False,
)

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

If you passed sessions_table= or messages_table= to the session, set SESSIONS_TABLE and MESSAGES_TABLE in Step 2 to the same names. I ran the session calls Runner.run makes (add_items, get_items, pop_item, clear_session) on SQLite with create_tables=False; I didn't run Runner.run itself or the MySQL URL.

Why False: with create_tables=True the SDK runs SQLAlchemy's create_all() before its first operation. PR #4741 notes that create_all(checkfirst=True) "leaves an existing table untouched", and on SQLite, create_tables=True on top of my Step 2 tables worked. But the SDK still builds its MySQL table definitions with the unbounded String, so False keeps it from compiling them at all. I didn't test True against a real MySQL server with existing tables. The API reference already recommends False for production.

Step 4: Check it worked

  1. Run the Step 2 script once, then run your app with create_tables=False.
  2. Confirm the first Runner.run(..., session=session) no longer raises the CompileError.
  3. Run a second turn with the same session ID and check that the agent sees the first turn. await session.get_items() should return the items from both turns.
  4. Optional: in a MySQL client, SHOW CREATE TABLE agent_messages; should show varchar(190) for session_id and longtext for message_data.

Skip the plumbing with MemorySync

Disclosure: I build MemorySync.

This doesn't fix the SQLAlchemySession bug, and if you need your session history in your own MySQL database, Steps 2 and 3 are the way. If MySQL was only where you planned to keep conversation history, the MemorySync OpenAI Agents SDK guide documents MemorySyncSession, which implements the SDK's Session protocol for Runner.run(..., session=...) and keeps conversation history on MemorySync's side, so there's no session table to create or migrate. Per the guide, items come back "exactly as the SDK wrote them", and user messages are also sent to fact extraction so durable facts become that user's long-term memories.

From the guide:

Unverified (not run):

pip install openai-agents-memorysync openai-agents
from agents import Agent, Runner
from openai_agents_memorysync import MemorySyncSession

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
)

session = MemorySyncSession(
    "thread-42",          # the conversation
    user_id="customer-7", # the end user it belongs to — required
)

result = await Runner.run(agent, "I'm vegetarian and I fly aisle.", session=session)

The guide says it needs Python 3.10+ and MEMORYSYNC_API_KEY in the environment (or api_key passed explicitly). Snippet adapted from the MemorySync OpenAI Agents SDK guide; I haven't run it for this page.

Try it: MemorySync is optional here: the steps above work without it. If you want the memory layer from the snippet, see MemorySync, create an API key in the dashboard and drop the snippet into your agent.

Troubleshooting

  • no such table / "doesn't exist" errors after switching to create_tables=False. The Step 2 script didn't run against the same database (or used different table names). On SQLite, skipping Step 2 gave no such table: agent_sessions on the first add_items().
  • The same CompileError after Step 2. Something still has create_tables=True on a MySQL URL, often a second place that builds the session (a worker, a test fixture). Search your code for create_tables=True.
  • An import error for aiomysql. The sqlalchemy extra doesn't install aiomysql; per the docs, install 'aiomysql[rsa]' alongside it.
  • You already created the tables by hand with VARCHAR(255). That's fine for create_tables=False; the SDK reads and writes by column name. Just make sure message_data is big enough for your largest items (see LONGTEXT above).
  • You use Redis Cluster instead. That's a different session backend with a different error; see Fix "'ClusterPipeline' object has no attribute 'raise_first_error'" with OpenAI Agents SDK RedisSession on Redis Cluster.

Common questions

Does this affect SQLiteSession or PostgreSQL? No. In my compile check, the SDK's tables compiled for SQLite and PostgreSQL on both versions. The problem is specific to MySQL and MariaDB.

Will a future SDK release fix this? Maybe. The maintainers said they "may revisit it in the future". Until then, the Step 2 tables keep working with create_tables=False. If a release adds MySQL-safe types, check its changelog before switching back to create_tables=True.

Do I need to change existing data? Not for this error: it happens before any table exists, so there's nothing to migrate. If you already have tables with a TEXT message_data column, PR #4910 notes it doesn't migrate existing tables; changing the column to LONGTEXT is your call.

Is 190 characters enough? It's enough for UUIDs and most user-123:thread-456 style IDs. If you need longer IDs, raise the length in Step 2 and check that your MySQL version and row format allow the longer index key.

Sources

  • github.com/openai/openai-agents-python/issues/4740 (opened 2026-08-28, closed as completed 2026-09-26 IST, 3 comments; exact error, reproduction, versions; read via the GitHub API 2026-10-31, re-checked 2026-11-14)
  • github.com/openai/openai-agents-python/pull/4741 (closed without merging 2026-09-25; VARCHAR(190) + utf8mb4_bin design, 767-byte reasoning, review notes, maintainer's closing comment; diff and comments read 2026-10-31)
  • github.com/openai/openai-agents-python/pull/4743 (merged 2026-09-07; driver installation docs)
  • github.com/openai/openai-agents-python/pull/4910 (closed without merging 2026-09-25; LONGTEXT reasoning and the author's live MySQL/MariaDB results)
  • PyPI: openai-agents 0.22.0 and 0.24.0 (release times; installed agents/extensions/memory/sqlalchemy_session.py); the same file on the main branch (read 2026-10-31, re-read 2026-11-14)
  • OpenAI Agents SDK docs: SQLAlchemy sessions, Sessions overview, SQLAlchemySession API reference (fetched 2026-10-31)
  • SQLAlchemy docs: TypeEngine.with_variant() (docs.sqlalchemy.org, core type API)
  • Offline runs (2026-10-31): the SDK's tables compiled with SQLAlchemy's mock engine for MySQL, MariaDB, PostgreSQL and SQLite on openai-agents 0.22.0 and 0.24.0; the Step 2 script compiled for MySQL and MariaDB and run against SQLite; Step 3 session calls on SQLite; no network, no database server, no keys
  • MemorySync OpenAI Agents SDK guide: docs.memorysync.io/guides/openai-agents (fetched 2026-10-31)

Try it

For an overview of MemorySync, see the MemorySync homepage.

Next step: read the MemorySync OpenAI Agents SDK guide, then see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

About the author

  • Building MemorySync (memorysync.io) — persistent memory infrastructure for AI agents, Cursor, and Claude Code.

No comments yet. Be the first to comment.