Repository navigation
v1.4.0
Every deployment should upgrade: the built-in admin account was refused by
admin-only agents on a new installation, a disabled webhook trigger still ran,
and two pages could run script stored by a widget visitor or a widget key's
holder. The release also brings a fallback model for provider outages, a
Content-Security-Policy (Report-Only), the documentation inside the
dashboard with a help agent behind a ? button, and memory blocks that
honour their Read-only and Character Limit settings.
Migrations V034 to V036 apply on startup. V035 replaces a Tavily API key that
older seeds wrote into agents' MCP URLs with ${TAVILY_API_KEY}; set that
variable if an agent uses Tavily.
Before upgrading:
- Install from
requirements.lock(pip install -r requirements.lock); Docker
and CI already do.requirements.txtis now only the list of direct
dependencies, and SQLAlchemy is pinned below 2.1. - With
MATE_ENV=production, the server exits at startup when it cannot reach
the database instead of serving an empty dashboard, and ADK's dev UI at
/dev-uiis off unlessADK_DEV_UI=true. - A memory block marked Read-only can no longer be changed or deleted, and a
value longer than its Character Limit is refused, whether the write comes
from an agent, the dashboard, the widget admin API, a trigger or a template
sync. An agent that relied on writing such a block now gets an error; untick
Read-only or raise the limit if that write is wanted. - Saving a trigger with an invalid cron expression, an
http_callbackwithout
a URL, or an email output without a recipient orSMTP_HOSTis now a400.
Existing triggers like that keep their config, but their runs are recorded
as errors instead ofok. docker-compose.ymlnow loads.envinto the container, so every setting
in it reaches MATE, andDB_TYPEandARTIFACT_SERVICEcome from it
(defaultssqliteandlocal_folder; it used to forcesupabase).- The Slack integration's Mention-only box is gone. It was never read: in
channels the agent has always answered only when mentioned. python shared/migrate.py rollbackis removed; it failed for every
migration. Reverse a change with a new migration or restore a backup.
Added
- Documentation in the dashboard - the documentation was a folder of Markdown files nobody saw from inside the product, and it drifted from the code. It now lives in
docs/and is served at/dashboard/docswith search: user guides, developer guides, and a reference (configuration, HTTP API, tools, database) thatscripts/gen_docs.pygenerates from the code. CI fails when the reference is stale and warns when code covered by a guide changes without the guide. The feature guides fromdocuments/moved intodocs/; pages not yet re-checked against the code are marked as such. Seedocs/dev/documentation.md - Content-Security-Policy - MATE sent no CSP, so an injected script could load code from any host and send data to one. Every page now gets a policy that limits scripts, styles, fonts and connections to MATE itself and the CDNs its templates use, forbids plugins and
<base>rewrites, and lets only MATE frame the dashboard. The widget chat page takes itsframe-ancestorsfrom the widget key's allowed origins whenWIDGET_ORIGIN_STRICTis on, covering the same sites as the origin check, so once enforced it also stops a site that hides its referrer. It ships asContent-Security-Policy-Report-Only(CSP_MODE): nothing is blocked, and violations are logged once each through/csp-report.CSP_EXTRA_SOURCESadds hosts a deployment needs. Inline scripts are still allowed; removing them is the next step. Seedocs/dev/content-security-policy.md(#124) - Fallback model - when an agent's model failed, the person chatting saw an error, even with healthy providers one config field away. An agent can now name a Fallback Model (agent modal, visual builder, or
fallback_modelon the agents API). A request whose model call fails because the provider is unavailable (a timeout, a connection failure, a 5xx or a 429) is re-run once on it, and the answer goes back without an error. Errors the request itself causes, such as a content-policy refusal or an oversized context, do not fall back, so a refusal cannot be used to get past the primary provider's filters. Guardrails and token logging apply to it as to any answer, usage logs book its tokens under the fallback model, and each fallback leaves a warning and anagent.model_fallbackaudit entry. The fallback is reached through the provider env vars, never the agent's own base URL and key. Works on both runtimes. Migration V034. Seedocs/user/fallback-model.md(#41) - Suggest a fix can change the memory blocks the agent read - when a wrong answer came from a block, such as outdated opening hours, the fix could only patch the instruction around it. The suggestion now also lists the blocks the agent read in that answer (from the rated turn, or by running the test case once in memory), and the model may revise the values of the ones you tick; read-only blocks are never offered. The check runs the suite with the new values standing in for the stored ones inside the in-memory run, without writing them, and warns which other agents of the project share the blocks. Apply is refused if a block changed meanwhile, and writes each block as a block version under your name, listed in the audit entry. See
docs/user/evals.md(#136) - Evals against a stored version on the LangGraph runtime - with
AGENT_FRAMEWORK=langgraph, running an eval against a version, a suite run and "Check against suite" returned501. They now build the graph from the version's snapshot, with sub-agents from their current config, and run it against an in-memory checkpointer, as the ADK runtime does. The deployed graph, the LangGraph session tables and the configured artifact storage are untouched, and RBAC is skipped for the eval run on both runtimes. Seedocs/user/evals.md(#123) - Help agent - a ? button on every dashboard page opens a chat with
mate_help, a built-in agent that searches and reads MATE's documentation through the newdocstool and knows which page you are on. Admins get every section with links to the Documentation page; other users get the user guides only. Migration V036 creates it in its own MATE Help project, open to the admin and user roles, with no model set, so it uses the server's default until an admin picks one; it is only created when no agent of that name exists. Seedocs/user/help.md - The database in
/health-/healthnow answers503with"database": "unavailable"when the database cannot be reached, so the DockerHEALTHCHECKand orchestrators notice. PostgreSQL and MySQL are retried at startup (DB_CONNECT_RETRIES, default 5, 3 s apart) for a database that starts after MATE, and withMATE_ENV=productionthe server exits if it still cannot connect (#144) - Hashed lock file -
requirements.lock, compiled with uv for Python 3.11 on every platform, pins every package with hashes. Docker, CI and the Quick Start install from it, so rebuilding the same commit can no longer pick up a breaking release. The README says how to regenerate and upgrade it (#143)
Changed
- ADK's dev UI is off in production by default -
/dev-uiis a debugging tool that shows admins every event, tool argument and session state, runs on the dashboard's origin, and needs'unsafe-eval'. WithMATE_ENV=productionit is no longer served unlessADK_DEV_UI=true; elsewhere nothing changes. The CSP only allowseval()there while it is served. The dashboard's Chat button already opens the Work Room, so nothing in the dashboard depends on it - The Slack integration's Mention-only option is removed - the form saved it but nothing read it: in channels the agent always answered only when mentioned, plus direct messages. The Slack guide says so (#154)
migrate.py rollbackis removed - it looked forR<version>files no migration ships, in the wrong folder, so it failed for every migration. The CLI now says to write a reversing migration or restore a backup (#156)- The dashboard's old dev UI chat side panel is removed; nothing could open it, as the Chat button opens the Work Room
Fixed
- Memory blocks ignored Read-only and Character Limit. Both were stored in a block's metadata and never checked. Changes, deletes and restores of a read-only block are now refused unless the write clears the flag, as is a value over the limit, with
error_coderead_onlyortoo_longand a message an agent can act on. The dashboard's edit form now saves the Read-only, Character Limit and Preserve on migration fields it always sent, a trigger whose answer cannot be written to its block fails the run instead of dropping the answer, and template sync skips such blocks and lists them in the sync dialog. Importing agents with overwrite still replaces blocks directly (#153) - Triggers reported success when nothing was delivered or scheduled. An invalid cron expression, an
http_callbackwithout a URL and an email output without a recipient orSMTP_HOSTwere saved without complaint, and their runs recorded asok. Create and update now answer400with the reason (update only checks when it changes the type, schedule or output, so rotating an older trigger's key still works), and such a run is recorded as an error with the agent's answer kept (#151) docker-composedid not pass.envto the container, so LLM API keys and every setting not listed underenvironment:never reached MATE. It is loaded withenv_filenow (optional), and the explicit entries still win.DB_TYPEandARTIFACT_SERVICEare taken from.envinstead of being forced tosqliteandsupabase, andDB_PASSWORDis no longer required (#159, #163). Thanks to @cestercian- The agent server was started with
pythonfromPATHinstead of the interpreter running the auth server, so in a virtualenv it could start without MATE's dependencies. It usessys.executablenow (#155). Thanks to @cestercian - Migrations failed on a fresh PostgreSQL database and on schemas made by
create_all. V002's seed relied on a unique constraint theMemoryBlockmodel lacked, so when the ORM created the table first every insert failed; the model declares it, V002 adds it when missing, andcreate_allonly runs after migrations succeed (#145). On tables made bycreate_all, which have no database-side defaults, V001 to V003 and V036 left NOT NULL timestamps and flags to the database and failed (V036 was even recorded as applied on SQLite without creating the agent); they set every such column themselves now, and the V001 and V003 agent seeds only insert missing agents instead of overwriting ones an admin may have edited migrate.py createwrote a single dialect. It now writes the new file intosqlite/,postgresql/andmysql/, numbered after the highest version in any of them, andgen_docs.py --checkin CI fails while a version is missing from a dialect (#156)- The server ran without a database after a rebuild - SQLAlchemy 2.1 made
postgresql://select psycopg 3, which is not installed, so the first image built after its release could not open the database and the dashboard silently showed nothing. SQLAlchemy is pinned to 2.0, and the sync engines namepostgresql+psycopg2://explicitly - The Gemini image generation setup check always failed with
google-genai; it called the oldgoogle-generativeaiAPI and now creates agenai.Client - Script errors on the visual builder and the API docs page - with no project selected the builder rendered
Noneinto its script and stopped, and the API docs page called a function before defining it - The last items of the sidebar were hidden on phones, Documentation among them; the mobile drawer now fits the visible screen
Security
- Script in a widget's config - the widget chat page put the key's config into an inline
<script>withjson.dumps, which leaves</script>intact. Whoever holds a widget's admin key could set a title that closed the script and ran their own on MATE's origin, the dashboard's, when an admin opened the widget preview. The config, agent name and key are rendered withtojsonnow - Script in the dashboard home's Recent Activity - the list put an audit entry's actor, action and resource into the page unescaped. The actor can come from a chat: a widget visitor picks their own
user_id, which an RBAC denial (and now a model fallback) records. A visitor could thus store script that ran when an admin opened the dashboard. The fields are escaped now, as on the Audit Logs page - The built-in admin account was not an admin to agents. The
AUTH_USERNAMEaccount administers the dashboard, but itsusersrow was created with only theuserrole, so on a new installation every admin-only agent refused it and no agent could write memory blocks for it. The row is now created withadminanduser, andadminis added to an existing row that lacks it (#152) - Disabled webhook triggers still fired.
is_enabledwas only checked for cron triggers, so a switched-off webhook trigger still ran when its URL was called with a valid key.POST /triggers/{id}/firenow answers409for a disabled trigger, after authentication and the signature check. Test-fire from the dashboard still runs it (#150) - A Tavily API key was shipped in seed data - the V001 seed, three agent templates and the docs carried a real key in the Tavily MCP URL. They now use the
${TAVILY_API_KEY}placeholder the MCP loader fills from the environment, and V035 swaps the key out of agents seeded with it /csp-reportand the widget allowlist were hardened - the anonymous report endpoint refuses bodies over 16 KB before reading them, ignores malformed fields instead of failing with a500, and logs at most 100 violations per 10 minutes. A non-string entry in a widget key's allowed origins no longer makes the chat page a500, and an unrecognisedADK_DEV_UIvalue no longer turns the dev UI on in production- Help answers could link off-site - a link starting with
/\, which browsers read as//host, passed as a local link in the help panel; it is refused now
Full Changelog: v1.3.3...v1.4.0