> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agno.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agno v3.0 Changelog

> Full list of storage, parameter, and behavior changes introduced in Agno v3.0.

This release rebuilds the storage layer around a normalized runs table, extends
per-user isolation across the platform, and makes AgentOS background execution
durable.

The major changes are:

* Session runs are stored one row per run in a dedicated runs table.
* `user_id` scoping extends to metrics, schedules, evals, knowledge and vector databases.
* `background=True` on AgentOS is backed by a durable job queue that survives crashes and deploys.
* Database migrations run through the built-in `MigrationManager`, with schema versions tracked on every adapter.

## Storage

<Accordion title="Denormalized session storage">
  * Runs are no longer stored as a JSON blob in the sessions table. Each run is
    a row in the runs table (`agno_runs` by default) with `run_id`,
    `session_id`, `run_type`, `run_index`, `user_id`, `status` and `run_data`.
  * Saving a run writes one row instead of rewriting the whole session history.
    This removes the quadratic write amplification and unbounded row growth of
    the blob design.
  * `session.runs` is still populated on read: sessions merge the runs table
    with any legacy blob, so un-migrated sessions keep working.
  * New direct accessors: `db.get_run(run_id)` and
    `db.get_runs(session_id=..., user_id=..., status=..., limit=...)`.
  * The v2 -> v3 migration preserves the legacy `runs` column as a backup.
    Reclaim it with `db.cleanup_legacy_runs_column(force=True)` (SQL) or
    `db.cleanup_legacy_runs_field(force=True)` (document/KV adapters) after
    verifying the migration — the migration keeps every legacy blob as a backup,
    so the unforced call refuses by design.
</Accordion>

<Accordion title="SQLite runs in WAL journal mode">
  * `SqliteDb` and `AsyncSqliteDb` issue `PRAGMA journal_mode=WAL` on connect,
    replacing SQLite's default DELETE journal (a journal create, double fsync and
    delete on every commit).
  * WAL is persistent on the database file and produces `-wal` and `-shm`
    sidecar files next to it. Copy or back up all three together.
</Accordion>

<Accordion title="Built-in migrations and schema versioning">
  * `MigrationManager(db).up()` walks all registered migrations for every table
    and stamps the resulting schema version.
  * Schema versions are tracked on every adapter, including the document and
    key-value stores (MongoDB, Redis, Valkey, Firestore, DynamoDB, SurrealDB,
    JSON, GCS JSON, in-memory). An unstamped database is treated as pre-v3 and
    migrated.
  * Migrations are idempotent and non-destructive. Failures raise and abort
    before any version stamp is written.
</Accordion>

## Knowledge

<Accordion title="Insert API replaces add_content">
  * `Knowledge.add_content` -> `insert()`
  * `Knowledge.add_content_async` -> `ainsert()`
  * `Knowledge.add_contents_async` -> `ainsert_many()`
</Accordion>

<Accordion title="Vector database changes">
  * `LanceDb`: `use_tantivy` is removed; passing it now raises a `TypeError`.
  * On schema-based stores, searching a pre-v3 vector table with a `user_id`
    raises a `ValueError` directing you to the vector database migration,
    instead of silently returning empty results. Schemaless stores (Qdrant,
    Pinecone, Upstash, Chroma, MongoDB, OpenSearch, SurrealDB) treat pre-v3
    documents as shared.
</Accordion>

## User Isolation

<Accordion title="Per-user scoping across the platform">
  * `user_id` columns added to the schedules, schedule-runs and evals tables.
    All user-facing read and write methods accept `user_id`.
  * Metrics aggregate per user. The unique key changed from
    `(date, aggregation_period)` to `(user_id, date, aggregation_period)`.
    Sessions without a `user_id` aggregate into a shared bucket that
    `get_metrics` maps back to `None`.
  * Knowledge and vector database contents are scoped per user when isolation
    is enabled. On schema-based stores, searching a pre-v3 vector table with a
    `user_id` raises a `ValueError` directing you to the vector database
    migration; schemaless stores treat pre-v3 documents as shared.
  * Schedule polling (`claim_due_schedule` / `release_schedule`) stays
    unscoped so background execution fires across all users; each schedule run
    records the owner denormalized from its parent schedule.
</Accordion>

## AgentOS

<Accordion title="MCP server configuration">
  * `AgentOS(enable_mcp_server=..., mcp_config=...)` is replaced by a single
    `mcp_server=` parameter, which takes a bool or an `MCPServerConfig`.
</Accordion>

<Accordion title="Pagination is validated">
  * Passing `page` without `limit`, or a `page` below 1, now raises a
    `ValueError` instead of being silently ignored. Pages are 1-indexed.
</Accordion>

<Accordion title="Durable background execution">
  * Accepted `background=True` requests are committed job rows that survive
    crashes, restarts and deploys. Any replica's worker can claim and execute
    them.
  * Concurrency is bounded. Excess submissions wait in `pending` status instead
    of overloading the process.
  * Runs can be tailed (`stream=true`), resumed after a disconnect (`/resume`)
    and cancelled from any replica.
  * `Idempotency-Key` headers 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.
  * Background execution requires a `db` on the component and returns a 400
    without one.
  * External framework agents (LangGraph, Claude, DSPy, etc.) stream inline
    when `background=true` is requested; their runs are not resumable.
</Accordion>

<Accordion title="JWT middleware">
  * `secret_key` removed from `JWTMiddleware` and `authorization_config`. Use
    `verification_keys`, which takes a list of keys.
</Accordion>

<Accordion title="Metadata route consolidation">
  * `GET /models` removed. Model data moved into `GET /config` under
    `available_models`.
  * `GET /` returns a minimal landing response linking to `/docs`, `/info` and
    `/health`.
  * `GET /info` is the single unauthenticated metadata endpoint.
</Accordion>

## Agents

<Accordion title="Removed parameters">
  * `enable_user_memories` -> `update_memory_on_run`
  * `search_session_history` -> `search_past_sessions`
  * `num_history_sessions` -> `num_past_sessions_to_search`
  * `num_past_session_runs` -> `num_past_session_runs_in_search`
  * `reasoning=True` removed. Set `reasoning_model=<native reasoning model>`
    explicitly.
  * `continue_run` / `acontinue_run`: `updated_tools` removed. Pass
    `requirements` (a list of `RunRequirement` from the paused run output).
</Accordion>

<Accordion title="Culture feature removed">
  * The experimental culture feature is removed: `enable_agentic_culture`,
    `add_culture_to_context`, `CulturalKnowledge`, the culture tools and the
    `agno_culture` table.
  * Use [Knowledge](/knowledge/overview) for shared cross-user information.
</Accordion>

## Teams & Workflows

<Accordion title="Keyword-only Workflow constructor">
  * The `Workflow` constructor no longer accepts positional arguments:
    `Workflow(name=..., steps=[...])`.
  * `Team` still accepts `Team([agent_1, agent_2])`; the keyword form
    `Team(members=[...])` is preferred but not required.
</Accordion>

<Accordion title="HITL configuration unified into HumanReview">
  * Flat HITL kwargs on `Step`, `Steps`, `Loop`, `Condition` and `Router` are
    removed: `requires_confirmation`, `confirmation_message`, `on_reject`,
    `requires_user_input`, `user_input_message`, `user_input_schema`,
    `requires_output_review`, `output_review_message`,
    `requires_iteration_review`, `iteration_review_message`, `on_error`,
    `hitl_max_retries`, `hitl_timeout`, `on_timeout`.
  * Pass `human_review=HumanReview(...)` instead
    (import from `agno.workflow.types`). Field names are unchanged except
    `hitl_max_retries` -> `max_retries` and `hitl_timeout` -> `timeout`.
</Accordion>

## Tools

<Accordion title="Toolkit updates">
  * `MCPToolbox`: `auth_tokens` and `auth_headers` removed. Use
    `auth_token_getters`.
  * Toolkits have an `id`, used by AgentOS to reference tools stably.
</Accordion>

<Accordion title="Workspace refuses credential files by default">
  * `Workspace` now excludes env files (`.env*`, `*.env`) and conventional
    credential files — private keys and keystores (`*.pem`, `*.key`, `id_rsa*`),
    credential directories (`.ssh`, `.aws`, `.kube`), registry and host tokens
    (`.npmrc`, `.netrc`, `.git-credentials`), credential data files
    (`credentials.json`, `secrets.yaml`, `service_account*.json`) and Terraform
    inputs (`*.tfvars`).

  * An agent that reads one of these today starts getting a refusal. Re-allow a
    specific path explicitly:

    ```python theme={null}
    Workspace(".", allow_paths=["config/credentials.json"])
    ```

  * Committed templates go the other way and become readable
    (`.env.example`, `.env.sample`, `.env.template`, `.env.dist`).

  * `credentials.*` and `secrets.*` are deliberately absent from the list: they
    would also refuse ordinary source such as `credentials.py`.

  * Known limit: a hard link to an excluded file bypasses the boundary. Symlinks
    are caught.
</Accordion>

<Accordion title="Removed and renamed toolkits">
  * `MultiMCPTools` is removed, along with its `allow_partial_failure` parameter.
    Use one `MCPTools` per server.
  * The flat Google tool modules are removed: `agno.tools.gmail`,
    `agno.tools.googlesheets`, `agno.tools.googlecalendar`,
    `agno.tools.google_maps`, `agno.tools.google_drive` and
    `agno.tools.google_bigquery`. Import from `agno.tools.google.*` instead.
  * Google toolkits: `creds_path` -> `credentials_path`, `auth_port` ->
    `oauth_port`.
  * `SeltzTools`: `max_documents` -> `max_results`. Older `seltz` SDKs still
    work through a fallback; `seltz>=1.2.0` is needed for the `scope`, domain
    and date filters.
  * `BrandfetchTools`: the `async_tools` parameter is removed.
  * `StudioTool` -> `StudioTools`.
  * `GDriveContextProvider` -> `GoogleDriveContextProvider`.
</Accordion>

<Accordion title="Removed toolkit methods">
  * `DuckDuckGoTools.duckduckgo_search` -> `web_search`, and
    `DuckDuckGoTools.duckduckgo_news` -> `search_news`. The toolkit now builds
    on `WebSearchTools`, which supplies both methods.
  * `FileTools.check_escape` -> `Toolkit._check_path`. `LocalFileSystemTools`
    keeps its own `check_escape`, which is unaffected.
  * `BrightDataTools.get_screenshot`: the unused `output_path` parameter is removed.
  * `PgVector.enable_prefix_matching` is removed. It was a dead helper with no
    effect on search.
</Accordion>

## Scheduler

<Accordion title="Schedule provenance columns">
  * The schedules table gains eight nullable columns recording where a schedule
    came from and who last touched it: `managed_by`, `target_type`, `target_id`,
    `created_by_run_id`, `created_by_session_id`, `updated_by_run_id`,
    `updated_by_session_id` and `disabled_reason`. `managed_by` and `target_id`
    are indexed.
  * The v3.0.0 migration adds the columns and indexes on SQLite and PostgreSQL
    (sync and async). Existing rows are left as-is with `NULL` provenance; no
    data is rewritten. MongoDB needs no schema change.
</Accordion>

<Accordion title="Schedule names are unique per user">
  * The schedules unique key becomes `(user_id, name)`.
  * If duplicate schedule names already exist, the v3.0.0 migration **aborts**
    rather than stamping itself as done. Resolve the duplicates and re-run.
</Accordion>

<Accordion title="update_schedule column allow-list">
  * `update_schedule` can only write `name`, `description`, `method`,
    `endpoint`, `payload`, `cron_expr`, `timezone`, `timeout_seconds`,
    `max_retries`, `retry_delay_seconds`, `enabled`, `next_run_at` and
    `disabled_reason`. Any other key raises a `ValueError` naming the rejected
    columns.
  * Ownership, provenance and lock state are no longer writable through the
    generic update path, so a name-keyed upsert cannot repoint who a schedule
    belongs to or what it targets.
</Accordion>

## Evals

<Accordion title="Result file placeholder renamed">
  * `store_result_in_file`: the `eval_id` parameter is renamed to `run_id`.
  * `{eval_id}` is no longer accepted in `file_path_to_save_results` templates.
    Use `{run_id}`.
  * `POST /eval-runs` now returns the id the row was actually stored under
    (`run_id`), instead of the eval object's `eval_id`. Every eval run gets its
    own `run_id`.
  * The eval classes no longer carry `eval_id`; results carry a per-run `run_id`,
    so re-runs no longer overwrite each other's stored results.
</Accordion>

## Models

<Accordion title="Removed model APIs">
  * The `agno.models.metrics` module and its `Metrics` alias are removed. Use
    `agno.metrics` and `RunMetrics`.
  * `Model.classify_error` -> `ModelProviderError.classify(error)`.
</Accordion>

<Accordion title="Mistral requires mistralai v2">
  * The `mistralai` v1 compatibility layer is removed. `agno[mistral]` now
    requires `mistralai>=2.0.0`.
  * `agno[mistral]` is included in the `models` extra again.
</Accordion>

<Accordion title="Default model updates">
  * `Cerebras` and `CerebrasOpenAI` default to `gpt-oss-120b`, replacing
    `llama-4-scout-17b-16e-instruct`.
  * OpenAI `reasoning_effort`, `reasoning_summary`, `service_tier` and
    `verbosity` accept the full set of API values (including `none`, `xhigh`,
    `max`, `scale`, `fast`, `ultrafast`) and any future string. This widens the
    accepted types; no existing call breaks.
</Accordion>

## Learning

<Accordion title="Entity memory is isolated per user">
  * `EntityMemoryStore` with `namespace="user"` did not isolate users: the row
    key carried no user component, so two users recording the same entity name
    and type shared one row. One user's facts overwrote the other's and leaked
    into their prompt context.
  * Row keys under `namespace="user"` now embed a digest of the `user_id`.
    Global and custom namespaces are unchanged and do not re-key.
  * Pre-v3 rows are re-keyed by the v3.0.0 migration, not at runtime. Run it
    with the rest of your migrations, or call
    `agno.learn.migrations.rekey_user_entity_learnings` directly. The
    migration's `down()` refuses to reverse the re-key, since the old key
    collides users by design.
  * `EntityMemoryStore.delete` / `adelete` take a keyword-only `user_id` and
    refuse `namespace="user"` deletes without it. `get` / `aget` now require a
    `user_id` in that namespace instead of returning an arbitrary user's row.
</Accordion>

<Accordion title="Studio authors learning, not legacy memory">
  * `enable_agentic_memory` and `memory_manager_id` are removed from every Studio
    create/edit form (sync and async). Studio components declare memory through
    `LearningMachine` instead: `learning_name` binds a registry-declared machine,
    or `enable_learning=True` builds the default one.
  * The `Agent` and `Team` constructor parameters are unchanged, as are
    `Registry.memory_managers` and `resolve_memory_manager_reference`, so configs
    stored with the legacy fields keep rehydrating. Only the Studio authoring
    surface dropped them.
  * Enabling learning on a component (a learning machine actually configured
    via `learning_name` / `enable_learning`) clears `enable_agentic_memory` and
    `memory_manager`: both register a tool named `update_user_memory`, and the
    legacy one silently shadowed the store's. Setting them to `False`/`""`
    leaves the legacy pair alone.
</Accordion>

<Accordion title="Removed learn aliases">
  * `MemoriesConfig` -> `UserMemoryConfig`
  * `MemoriesStore` -> `UserMemoryStore`
  * `Decision` -> `DecisionLog`
</Accordion>

## Packaging

<Accordion title="Postgres extra installs a working driver">
  * `agno[postgres]` installed `psycopg-binary` only, which ships the C
    accelerator but no importable `psycopg` and no engine layer, so
    `PostgresDb` raised `ModuleNotFoundError: No module named 'sqlalchemy'` on
    a clean install. The extra now installs `psycopg`, `psycopg-binary` and
    `sqlalchemy`.
  * If you worked around this by installing `psycopg` or `sqlalchemy`
    yourself, you can drop those pins.
</Accordion>

## Errors

<Accordion title="Clearer schema mismatch errors">
  * SQL adapters raised `Table <name> has an invalid schema` with no next step.
    The error now names the likely cause (a database created by an older Agno
    version) and points at both fixes: `asyncio.run(MigrationManager(db).up())`
    or `POST /databases/all/migrate` on AgentOS.
</Accordion>
