Installing Agno v3
If you are already using Agno, you can upgrade to v3 by running:Migrating your Agno DB
The built-in migration makes two schema changes:- Session runs move to their own table. In v2, every session row held its
full run history as a single JSON blob in the
runscolumn. In v3, each run is its own row in a dedicated runs table (agno_runsby default), which removes the write amplification and unbounded row growth of the blob design. - On the SQL adapters, a
user_idcolumn (with index) is added to the evals, components, knowledge, schedules, schedule-runs and metrics tables, for user isolation. The metrics unique key changes from(date, aggregation_period)to includeuser_id. Document and KV backends need no schema change here: per-user scoping on those comes from the v3 write path, so on them the migration only moves the runs.
migrate_to_v3.py
On the async adapters (
AsyncPostgresDb, AsyncMySQLDb, AsyncSqliteDb,
AsyncMongoDb) get_runs and the cleanup method are coroutines — await them:
runs = asyncio.run(db.get_runs(limit=5)) and
asyncio.run(db.cleanup_legacy_runs_column(force=True)) (on AsyncMongoDb
the method is cleanup_legacy_runs_field).libs/agno/migrations/v2_to_v3
(migrate_sql_vectordbs.py, migrate_field_vectordbs.py or
migrate_sentinel_vectordbs.py, depending on your vector store) to add
user_id scoping to existing collections. On the schema-based stores (PgVector,
SingleStore, LanceDB, Milvus, ClickHouse, Redis, Cassandra, Couchbase) an
un-migrated table raises a ValueError on user-scoped searches instead of
returning empty results. Schemaless stores (Qdrant, Pinecone, Upstash, Chroma,
MongoDB, OpenSearch, SurrealDB) need no migration: pre-v3 documents stay
visible to every user as shared.
Notes:
- The migration is non-destructive and idempotent: the legacy
runscolumn is preserved as a backup, and re-running the migration never duplicates runs. - Reads keep working before, during and after the migration. Sessions merge the runs table with any legacy blob, so an un-migrated session still shows its history.
cleanup_legacy_runs_column()refuses to run while legacy data is present unless you passforce=True. Only passforce=Trueafter Step 2 passes. Cleanup permanently deletes the blob, which is the only copy of your history if the migration did not actually copy it.- Supported everywhere sessions are stored: Postgres, MySQL, SQLite, SingleStore, MongoDB, Redis, Valkey, Firestore, DynamoDB, SurrealDB, JSON, and GCS JSON, plus the async Postgres, MySQL, SQLite and MongoDB adapters.
Migrating your Agno code
Each section covers one breaking change, with before and after examples.1. Sessions and runs (denormalization)
Reading sessions is unchanged.session.runs is still populated, now from the
runs table:
v3_sessions.py
runs column of the sessions table directly (SQL, dashboards,
exports), point those queries at the runs table instead. After cleanup the
column no longer exists:
2. Workflow HITL: flat kwargs → HumanReview
Workflow primitives no longer accept flat HITL kwargs. All human-in-the-loop
configuration lives in one HumanReview object.
This is how it looked in v2:
v2_hitl.py
v3_hitl.py
HumanReview, except
hitl_max_retries → max_retries and hitl_timeout → timeout. This applies
to Step, Steps, Loop, Condition and Router.
3. Removed and renamed parameters
These deprecated parameters have been removed. Update them to their v3 names:Agent and Team constructors:
v3_agent_params.py
continue_run / acontinue_run: the updated_tools parameter is removed.
Pass requirements (a list of RunRequirement, available on the paused run
output) instead of a modified ToolExecution list:
v3_continue_run.py
authorization_config: secret_key is removed. Use
verification_keys, which takes a list:
v3_jwt.py
MCPToolbox: auth_tokens and auth_headers are removed. Use
auth_token_getters (same shape: a mapping of auth source names to token
callables).
4. Reasoning requires an explicit model
Thereasoning=True shortcut has been removed. Pass a native reasoning model
explicitly:
v2_reasoning.py
v3_reasoning.py
5. The Workflow constructor is keyword-only
Workflow no longer accepts positional arguments:
v2_workflow.py
v3_workflow.py
Team is unchanged: Team([agent_1, agent_2]) still works. The keyword form
Team(members=[...]) is preferred for clarity but is not required.
6. User isolation: user_id across the platform
With user_isolation enabled on AgentOS, data is now scoped per user across
memories, knowledge, evals, metrics, schedules and vector databases, in
addition to sessions. What this means for your code and data:
user_idcolumns were added to the schedules, schedule-runs and evals tables; the built-in migration handles this.- Metrics aggregate per user: the unique key changed from
(date, aggregation_period)to(user_id, date, aggregation_period). Deployments without isolation see the same single-row-per-date shape as before; sessions without auser_idaggregate into a shared bucket. - Vector database collections created before v3 have no per-user scoping. On
schema-based stores, searching them with a
user_idraises aValueErrortelling you to run the vector database migration — an un-migrated table fails loudly instead of silently returning empty results. On schemaless stores (Qdrant, Pinecone, Upstash, Chroma, MongoDB, OpenSearch, SurrealDB) pre-v3 documents are simply treated as shared.
7. Background execution and durable queues
background=True on AgentOS is rebuilt around a durable job queue. In v2 it
spawned an unbounded asyncio.create_task, and a process death silently lost
every waiting and in-flight run. In v3:
- Accepted requests are committed rows that survive crashes, restarts and deploys; any replica’s worker can execute them.
- Runs are bounded by a concurrency cap; excess submissions wait in the
queue in
pendingstatus instead of overloading the process. - Every run can be watched (
stream=truetails), resumed after a disconnect (/resume) and cancelled from any replica. Idempotency-Keyheaders deduplicate resubmissions.- Redis is optional coordination (live event streams, cross-replica cancellation), never truth. A Redis fault degrades the live view; it cannot lose or corrupt a run.
db on the agent
(enforced with a 400), run status now transitions pending → running → completed (poll GET /agents/{id}/runs/{run_id} for the terminal state), and
external framework agents (LangGraph, Claude, etc.) stream inline, so their
runs are not resumable.
8. Culture feature removed
The experimental culture feature (enable_agentic_culture,
add_culture_to_context, CulturalKnowledge, the agno_culture table) has
been removed. Remove any references; if you need shared knowledge across users,
use Knowledge instead.
9. Entity memory is isolated per user
If you useEntityMemoryStore with namespace="user", your existing rows are
shared across users and must be re-keyed.
In v2 the row key carried no user component, so two users who recorded an
entity with the same name and type wrote to the same physical row: one user’s
facts overwrote the other’s and then appeared in their prompt context. In v3
the key embeds a digest of the user_id. Global and custom namespaces are
unchanged.
Pre-v3 rows are re-keyed by the migration, not at runtime — until you run
it, reads still match the old shared rows. The re-key is part of the v3.0.0
migration, so MigrationManager(db).up() (or POST /databases/all/migrate)
covers it along with everything else:
v3_rekey_entities.py
rekeyed moved to the owner’s key, and keyed was already
correct. merged is expected rather than an error — if the upgraded application
wrote to the user-scoped key before the migration ran, the entity exists in two
rows and they are folded together, with the newer row winning. conflicts and
failed need an operator: resolve them, then re-run the helper.
Rows whose stored content records a different user than their owner column held
two users’ data before the fix and cannot be separated. The migration moves
these to the quarantined_user namespace instead of deleting them: the content
is preserved and entity memory stops reading it. They remain listed and mutable
through the /learnings API for whichever user the owner column names. To
delete them instead — along with every row that has no owner — and let entity
memory re-capture from conversation, pass purge_unrecoverable=True.
Two API changes come with it:
delete/adeletetake a keyword-onlyuser_idand refusenamespace="user"deletes without it. Previously any caller could delete another user’s entity by name.get/agetrequire auser_idin that namespace instead of returning an arbitrary user’s row.
10. Smaller changes
- AgentOS metadata routes:
GET /modelswas removed (its data moved intoGET /configunderavailable_models), andGET /is now a minimal landing response.GET /infois the single unauthenticated metadata endpoint. - Toolkits have an
id, used by AgentOS to reference tools stably. - Schedule provenance columns: the schedules table gains eight nullable
columns (
managed_by,target_type,target_id,created_by_run_id,created_by_session_id,updated_by_run_id,updated_by_session_id,disabled_reason), added by the v3.0.0 migration on SQLite and PostgreSQL. Existing rows keepNULLprovenance and no data is rewritten, so this needs no action beyond running the migration. If you query the schedules table directly withSELECT *, expect the extra columns. update_scheduleis restricted to a column allow-list: it now writes onlyname,description,method,endpoint,payload,cron_expr,timezone,timeout_seconds,max_retries,retry_delay_seconds,enabled,next_run_atanddisabled_reason. Passing a provenance column raises aValueErrorinstead of silently repointing the row’s owner or target.user_idis not an update field either: it scopes the update to that owner, so an update passing the wronguser_idmatches nothing.- Removed toolkit methods:
DuckDuckGoTools.duckduckgo_search->web_searchandduckduckgo_news->search_news;FileTools.check_escape->Toolkit._check_path;PgVector.enable_prefix_matchingremoved (dead helper);BrightDataTools.get_screenshotno longer takesoutput_path. - Removed learn aliases:
MemoriesConfig->UserMemoryConfig,MemoriesStore->UserMemoryStore,Decision->DecisionLog. - Eval result files:
store_result_in_file’seval_idparameter is nowrun_id, and{eval_id}is no longer accepted infile_path_to_save_resultstemplates — use{run_id}.POST /eval-runsreturns the id the row was stored under. Workspacerefuses credential files by default: env files and conventional credential paths (*.pem,.ssh,.aws,credentials.json,*.tfvars, …) are excluded, so an agent that reads one starts getting a refusal. Re-allow specific paths withWorkspace(".", allow_paths=["config/credentials.json"]). Committed templates such as.env.examplebecome readable.- Studio memory forms:
enable_agentic_memoryandmemory_manager_idare gone from the Studio create/edit forms. Uselearning_name(a registry machine) orenable_learning=True. TheAgent/Teamconstructor parameters are unchanged, so stored configs keep rehydrating. - SQLite uses WAL:
SqliteDb/AsyncSqliteDbconnect in WAL journal mode, which creates-waland-shmsidecar files next to the database. Copy or back up all three together. MultiMCPToolsremoved: use oneMCPToolsper server. Theallow_partial_failureparameter is gone with it.- Knowledge insert API:
add_content->insert(),add_content_async->ainsert(),add_contents_async->ainsert_many(). - Flat Google tool modules removed: import from
agno.tools.google.*instead ofagno.tools.gmail,agno.tools.googlesheets,agno.tools.googlecalendar,agno.tools.google_maps,agno.tools.google_drive,agno.tools.google_bigquery. Their parameters changed too:creds_path->credentials_path,auth_port->oauth_port. - Other toolkit renames:
SeltzTools.max_documents->max_results(olderseltzSDKs still work through a fallback;seltz>=1.2.0is needed for thescope, domain and date filters);BrandfetchToolsdropsasync_tools;StudioTool->StudioTools;GDriveContextProvider->GoogleDriveContextProvider. - AgentOS MCP config:
AgentOS(enable_mcp_server=..., mcp_config=...)->mcp_server=(a bool orMCPServerConfig). - Removed model APIs: the
agno.models.metricsmodule and itsMetricsalias are gone — useagno.metrics/RunMetrics.Model.classify_error->ModelProviderError.classify(error). LanceDb.use_tantivyis removed; passing it now raises aTypeError.- Pagination is validated:
pagewithoutlimit, orpage < 1, now raises aValueErrorinstead of being ignored. - Schedule names are unique per user: the unique key becomes
(user_id, name). If duplicate names already exist, the v3.0.0 migration aborts rather than stamping itself done — resolve the duplicates and re-run. - Mistral requires
mistralai>=2.0.0: the v1 compatibility layer is gone. Upgrade withpip install -U "agno[mistral]". - Cerebras default model:
CerebrasandCerebrasOpenAInow default togpt-oss-120binstead ofllama-4-scout-17b-16e-instruct. Pin the old id explicitly if you depend on it. agno[postgres]installs a working driver: the extra previously installedpsycopg-binaryonly, soPostgresDbfailed withModuleNotFoundError: No module named 'sqlalchemy'. It now pullspsycopgandsqlalchemy; you can drop any manual pins you added to work around it.
Migrate with a Coding Agent
Paste the prompt below into Claude, Cursor, or any coding agent with access to your repository. It applies the mechanical changes and flags everything that needs your judgment.Copy this prompt