Agents SDK session
The OpenAI Agents SDK keeps conversation history in a session. openai-agents-persql
(Python) and @persql/openai-agents (TypeScript) store those sessions in a PerSQL
database, so agent threads survive restarts and live in an isolated SQLite database
you can query like any other.
pip install openai-agents-persqlimport osfrom agents import Agent, Runnerfrom persql import PerSQLfrom openai_agents_persql import PerSQLSession
client = PerSQL(token=os.environ["PERSQL_TOKEN"])session = PerSQLSession("user-42", client.database("acme/agent-state"))
agent = Agent(name="assistant", instructions="Be helpful.")await Runner.run(agent, "hi", session=session)await Runner.run(agent, "remember me?", session=session) # history persistsAsync apps pass an async database handle instead — session calls are awaited natively rather than dispatched to a thread:
from persql import AsyncPerSQL
async with AsyncPerSQL(token=os.environ["PERSQL_TOKEN"]) as client: session = PerSQLSession("user-42", client.database("acme/agent-state")) await Runner.run(agent, "hi", session=session)The session creates two tables on first use (agent_sessions, agent_messages) —
the same layout as the SDK’s built-in SQLiteSession, so get_items, pop_item,
and clear_session behave identically.
TypeScript
Section titled “TypeScript”@persql/openai-agents is the same session for the JS Agents SDK:
npm install @persql/openai-agents @persql/sdkimport { Agent, run } from "@openai/agents";import { PerSQL } from "@persql/sdk";import { PerSQLSession } from "@persql/openai-agents";
const client = new PerSQL({ token: process.env.PERSQL_TOKEN });const session = new PerSQLSession("user-42", client.database("acme/agent-state"));
const agent = new Agent({ name: "assistant", instructions: "Be helpful." });await run(agent, "hi", { session });await run(agent, "remember me?", { session }); // history persistsBoth packages use the same table layout, but the two SDKs name tool-call fields
differently (callId vs call_id) — keep a given session’s history to one SDK.
One database per agent
Section titled “One database per agent”A session keys history by session_id inside one database. PerSQL’s shape lets
you go further: give each agent — or each run — its own database, so state,
blast radius, and spend are isolated per agent rather than commingled.
info = client.databases.create(f"agent-{run_id}", ttl_days=1)session = PerSQLSession(run_id, client.database(f"acme/{info['slug']}"))For ephemeral runs, lease a branch with a TTL and a scoped token instead — the branch (and its sessions) clean themselves up.
Inspect history with SQL
Section titled “Inspect history with SQL”Conversation items are rows. Operational questions that are awkward against an opaque store are one query here — via the console, MCP, or any SDK:
SELECT session_id, COUNT(*) AS items, MAX(created_at) AS last_activeFROM agent_messages GROUP BY session_id ORDER BY 2 DESC LIMIT 20;Local tests, no network
Section titled “Local tests, no network”Both SDKs’ local modes run the same session against in-process SQLite:
session = PerSQLSession("test", PerSQL(local=":memory:").database("test/db"))const session = new PerSQLSession("test", new PerSQL({ local: ":memory:" }).database("test/db"));Latency and cost
Section titled “Latency and cost”The Runner reads history once and appends new items once per turn, so a remote session adds two HTTP round-trips per turn — the same trade-off as the SDK’s SQLAlchemy session against a remote database. Items are metered like any other usage: a typical turn reads the history and writes a few rows. See pricing for rates.
Next step
Section titled “Next step”Pair the session with structured agent memory — the same database that holds the conversation can hold facts and episodes the agent recalls with SQL.