Skip to content

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 31 Oct 09:18
· 27 commits to main since this release
0b44b38

Added

  • User guide for personal access tokens (#235) - docs/user/personal-access-tokens.md explains who can have a token, how to create, use and revoke one, and how to use it as an OpenAI-compatible model or from an MCP client. Before, only the developer guides, which non-admins cannot open, explained them, and the help panel answered questions about tokens with the MCP server configuration instead. Users and roles and Tools and MCP servers link to it.

  • Cost per helpful answer (#275) - the Usage page's Satisfaction by Agent table shows what each answer rated helpful cost: the cost of every answer rated in the period, not helpful ones included, divided by the number rated helpful. A rating is joined to its answer's token rows through the invocation id, so the cost includes the sub-agents the answer handed to. Calls without a price are left out and counted, and an agent with fewer than 10 rated answers is shown in grey. The Evals Playground shows Cost per passed case per variant: its whole cost over the cases it passed, or no passes. See docs/user/usage-and-audit.md and docs/user/evals.md

  • Trace Replay shows which version of an agent answered (#274) - when an agent is built, MATE records the config it was built from: a short hash, and the id and number of the stored version that holds it, if one does. Each model call's gen_ai.inference span carries them (mate.agent.config_hash, mate.agent.config_version_id, mate.agent.config_version). Trace Replay shows the version on each agent node and in the step list. Clicking it opens the agent's version history at that version. A run on an older version than the agent has now is marked in amber, and a config no saved version holds (changed by an agent, a template import or the API) shows its hash, so two replays on different configs can be told apart. See docs/user/trace-replay.md and docs/dev/tracing.md

  • Route inventory test (#276, 1.6.0 item 1) - shared/test/test_route_inventory.py lists every /dashboard/api/ route of the real app from its router, those of included routers too (FastAPI keeps those out of app.routes), and requests each as a non-admin. It fails when a route answers anything but the middleware's 403, or when the allowlists in server/dashboard_authz.py let through a route the test does not list as reviewed. The four routes a non-admin may call today (reading and sending ratings, reading their own tokens, renaming a conversation) are the reviewed ones. The test is the skeleton of the 2.0 cross-workspace test. See docs/dev/auth-and-access.md

  • Secrets encrypted at rest (#276, 1.6.0 item 4) - with the new MATE_SECRET_KEY (a Fernet key, separate from SECRET_KEY), the secrets MATE stores and reads back are encrypted in the database: a Slack integration's bot token and signing secret, a webhook trigger's signing secret, and an agent's literal Model API Key, in its version history too. The columns use a new EncryptedText type (shared/utils/secret_box.py) that encrypts on write and decrypts on read, so nothing else changes. When the key is set on an existing installation, the auth server encrypts the values still stored in the clear at its next start. Without the key everything works as before, and the auth server warns at startup with the number of secrets stored in the clear; 2.0 will require it in production. A key that is not a Fernet key stops the auth server at startup. Keep the key: a secret stored with it reads as empty without it, and has to be entered again. Migration V045 makes the four columns TEXT, since an encrypted value no longer fits VARCHAR(255). cryptography is now a direct dependency. Checked on SQLite, PostgreSQL 16 and MySQL 8.0 by upgrading a database written by 1.6.0's previous step. See docs/dev/deployment.md

Changed

  • One request context for dashboard routes (#276, 1.6.0 item 2) - a dashboard route now learns who is calling from one dependency, get_request_context (server/request_context.py), which gives it the user and their role, instead of resolving the user and calling _get_is_admin itself. Every dashboard route uses it: the 24 pages, the API routes of dashboard_server.py, and the docs, widget-key and chat-integration routes, which take get_api_request_context so that a signed-out caller still gets 401 there rather than a redirect. _get_is_admin and _get_auth_user_dependency are gone, and 2.0 adds the workspace to the context. Nothing changes for users: a signed-out caller is still sent to the login page, and a non-admin to the Work Room. The route inventory test now also fails on a dashboard route that does not take the context. See docs/dev/auth-and-access.md

  • Log rows carry their project (#276, 1.6.0 item 3) - token_usage_logs, guardrail_logs, agent_responses and audit_logs get a project_id (migration V044), written with the row: the agent's project, or for an audit entry the project of the agent or project it names. The Usage page's cost by project reads it, so a call stays with the project it was made in when its agent is moved, renamed or deleted; before, every call was counted under the agent's current project, and under No project once the agent was deleted. Rows from before the upgrade are filled in from each agent's current project by a backfill the auth server runs once in the background, 10,000 rows at a time, so a large table is never locked for long. Checked on SQLite, PostgreSQL 16 and MySQL 8.0, and on a copy of a real database, where cost by project came out the same as before. See docs/dev/database-and-migrations.md

  • create_model() takes the caller's context (#276, 1.6.0 item 5) - create_model(), and create_chat_model() on the LangGraph runtime, take a ModelContext: the agent and project a model is built for. An agent's model and its fallback model get the agent, a subagent gets the agent that started it, and the fallback agent and the compaction summarizer get the installation's empty context. Provider keys still come from the environment, so nothing changes for users; the context appears in the "Creating ... model" log line, and 2.0 looks up a workspace's provider keys from it. shared/test/test_model_context.py fails on a call to either function that does not pass a context. See docs/dev/agent-runtime.md

Fixed

  • An agent created with the name of a deleted one inherited what was left of it (#276, found in the reviews of 1.5.1) - many tables refer to an agent by name, with no foreign key to cascade along, and deleting an agent left those rows behind. A new agent with the same name, perhaps in another project, then took them over:

    • its file-search store links, possibly to another project's stores;
    • its widget keys, which exposed the new agent publicly, and its Slack integrations and triggers;
    • its budgets, alert rules and eval test cases;
    • its users' stored OAuth credentials, their conversations on both runtimes, the memory built from them, and app-wide state.

    Every way of deleting an agent now removes them, in the same transaction: the dashboard's agent and project delete, the Create Agent tool and the wizard's trial cleanup (shared/utils/agent_cleanup.py). Usage, guardrail, feedback and audit logs stay. Checked on SQLite, PostgreSQL 16 and MySQL 8.0 against the session tables ADK creates. See docs/user/agents.md

  • A signed-in user could use up another user's request allowance (#276, found in the review of 1.5.1 C) - the rate-limit middleware counted a /run_sse request against the user id in its body before the proxy checked that id, so requests naming another user, which the proxy then refused with 403, still counted against that user's per-minute limit. A request that is refused is no longer counted. See docs/user/rate-limits.md

  • A LangGraph session created with the id of a deleted one resumed its conversation (#276, found in the review of #302) - deleting a session on AGENT_FRAMEWORK=langgraph removed its rows but not its checkpointed graph state. Deleting a session now deletes its checkpoint, and creating a session with a given id deletes any checkpoint left under it. See docs/dev/langgraph-runtime.md

  • The LangGraph runtime skipped remote MCP servers (#289) - on AGENT_FRAMEWORK=langgraph, an MCP server given by url was dropped with the warning "missing command or args", and the agent ran without its tools. It is now reached as on ADK: over streamable HTTP, or SSE with "transport": "sse", with headers, timeout and connect_timeout; ${NAME} in the url and headers is read from the environment and an unset one skips the server; only http and https are accepted; and an entry with both url and command uses the url. ${NAME} in a stdio server's command, args and env is now read from the environment too, as on ADK, instead of being passed on as written. A server that cannot be reached now costs only its own tools, not those of every server of the agent. See docs/user/tools-and-mcp.md

  • Slack users from different Slack workspaces shared one MATE user (#276, 1.6.0 item 6) - a Slack user's MATE id was slack_<user>, without the workspace, so the same Slack user id in two workspaces had one set of roles, one profile, one memory and one store of connected accounts (OAuth credentials). The id is now slack_<team>_<user>. On upgrade:

    • A Slack user's first message creates the new id with the roles of the old one, so no one loses access to a restricted agent, and the audit log records it.
    • The bot starts existing threads and direct messages afresh.
    • The old id keeps its conversations, usage and audit entries, which stay readable in the dashboard; its profile, memory and connected accounts are not carried over.

    See docs/user/slack.md

  • LangGraph checked roles only for the agent requested, not the one that answered (#276, 1.6.0 item 7) - on AGENT_FRAMEWORK=langgraph, a conversation transferred to a sub-agent goes on there in later turns, but each turn checked the user's roles only for the root agent requested. A user who lost the sub-agent's role, or whose sub-agent's allowed roles changed after the transfer, kept talking to it. Each turn now also checks the agent it resumes in and refuses it as ADK does; the user starts a new conversation to continue. A transfer with no run context, and so no user to check, was let through and is now refused, except in the graph built for LangGraph Studio. See docs/dev/langgraph-runtime.md

  • A subagent could send the provider's key to the parent agent's endpoint - a subagent started by subagent_delegation on its parent's model took the parent's model_base_url and model_api_key as stored, without resolve_agent_endpoint(), which builds the parent itself. With a base URL and no key, LiteLLM then sent the provider's key from the server environment to the host the base URL names, and a ${VAR} key was sent as the literal text ${VAR}. The subagent now reads the endpoint as the parent does: the variable is resolved, an unset one fails the subtask, and an endpoint without a key gets a placeholder. A subagent on another model given as default_model also took the parent's endpoint and key; now only a subagent on the parent's own model does. See docs/user/subagent-delegation.md

  • Only admins could create a personal access token (#276) - the API and the MCP servers accept a token of any user with a role in ALLOWED_API_ROLES (default admin, developer), and the account menu showed Personal Tokens to every signed-in user, but the dashboard's access check refused creating and revoking a token to everyone but admins, and the menu said only "Failed to generate token". A user with such a role now creates and revokes their own tokens. The menu shows Personal Tokens only to admins and those users, and creating a token refuses anyone else with 403, by the same rule the API applies when the token is used. An expires_in_days that is not a positive number now gets 400 instead of 500. See docs/dev/auth-and-access.md

  • Dashboard pages had up to three names (#288) - a page's sidebar entry, browser tab title and heading could each name it differently, such as Rate Limits, "Rate Limits & Budgets" and "Database Migrations". The tab title and heading now match the sidebar on every page; Usage is My Usage for non-admins, as in their sidebar. Thanks to @bernalalexis-try

  • Browser dialogs instead of the dashboard's own (#244) - actions that ask for confirmation on Sessions, Users, Memory Blocks, Migrations, Evals, the Work Room, the wizard pages, Integrations, Triggers and Widget Keys used the browser's confirm(), and the Migrations page had its own alert banner. They now use the dashboard's confirmation dialog and notifications, which follow the theme. Some alert() calls remain (#244). Thanks to @CRYPTONIKAV

  • Icon-only buttons and fields without a name for screen readers (#246) - the close buttons of every modal, the project edit and delete buttons, the copy buttons, the parent agents search clear button and the Rate Limits page's fields now have accessible names, and the Rate Limits labels are linked to their fields. Other pages follow in #246. Thanks to @Fire162 and @aoblak