Each run is now a row in a dedicated agno_runs table with real columns (session_id, run_type, status, agent_id, and more) alongside a JSON payload, rather than being packed into the session record. Reading is unchanged: session.get_messages(), get_chat_history(), db.get_session(), and the AgentOS session routes all behave exactly as before, because runs re-attach on read.
Previously every run lived inside the session blob, so each session write grew with the number of runs it held and eventually hit the item-size limits on stores like DynamoDB and Firestore. Now writes scale linearly and that ceiling is gone. You also get direct run APIs, so you can query and page runs without loading a whole session:
run = db.get_run(run_id="...")
runs = db.get_runs(session_id="...", status="completed", limit=20, page=1)
db.upsert_run(run)
db.delete_run(run_id="...")Because this changes your schema, a migration is required before v3.0 serves traffic. It is non-destructive and idempotent, runs on 12 sync and 4 async backends, and preserves the legacy column as a backup until you reclaim it:
import asyncio
from agno.db.migrations.manager import MigrationManager
asyncio.run(MigrationManager(db).up()) # or POST /databases/all/migrate on AgentOS
assert len(db.get_runs(limit=5)) > 0 # verify before any cleanupAn un-migrated database keeps working, and a stale one now raises a typed MigrationRequiredError that names the fix instead of misbehaving silently. Read the v3 Migration Guide before upgrading.



