v0.12.27
MCP — an agent can do everything Vexa does, and it knows how
The tools describe themselves: what to call, in what order, and what to say when something is
refused. whats_waiting gives an agent its own queue on connect, scoped to its caller, each item
carrying the sentence to say and the typed reason behind it — a person with no meeting yet gets a
first step instead of an empty answer. A refusal comes back as reason / message / action_url
and the deciding service's own words, not prose to scrape. A word the edge did not expect is passed
to the owning route rather than rejected at the door.
- Standing notices ride along with unrelated work (#1546, #1551, @DmitriyG228). An item whose
copy declares itself a standing notice travels on the meeting tools' own results — and on their
refusals — so an agent hears it without going looking.GET /queue/noticesasks for just those
sentences and nothing else. - Friction reporting (#1532, @DmitriyG228).
report_frictionfiles what did not work; no field
is required and no value a caller can send is refused — an unknown word, an over-long value or
a missing session are all filed rather than rejected, because a report we cannot tie back to a
conversation is worth strictly more than no report.friction_so_farreads your own back. - The queue's words come from files an admin edits, with no deploy on either side of the edit
(#1532, #1545, @DmitriyG228).
Flows — a tiny n8n you can vibecode in Python
Event queuing, triggering and scheduling at the top-level API, so you can chain bots, agents,
calendar events — anything. POST /flows takes a name, a trigger and an ordered list of step names,
validated against the deployed vocabulary at submission and live in the worker in about ten seconds;
the API never accepts code, and steps are reviewed Python in the image. Ten Postgres tables in the
stack's own database — no message broker, no scheduler, no second state store. Dedup is a UNIQUE
constraint, "every effect exactly once" is a receipt, and every wait is a timestamp column.
Flows are global, not per-user: this is what you set up for a company when self-hosting, to chain
post-meeting summaries and the like.
flows-api,flows-workerand the optionalflows-mailbox(#1456, @DmitriyG228) — one image,
three commands. Documented at Workflows.- The mailbox decides who it will act for before it admits anything (#1456, @DmitriyG228). An
empty allow-list means the mailbox's own domain, not everyone; anybody else gets no account, no
agent turn and no model call — onemail_quarantinerow, readable with oneSELECT. - Tests, fixtures and the flows documentation (#1497, @DmitriyG228).
Meetings are searchable, and your agents can own them
Full-text search over your own transcripts (GET /transcripts/search; the index builds itself out of
band on first boot, without locking the table). Annotation from the API and from MCP: POST /meetings/{id}/annotate attaches a title and arbitrary metadata during or after a meeting, and
GET /meetings?metadata= filters on it in the database (16 KB and 64 keys per meeting).
Once an agent annotates a meeting, it owns it — it tags each call with the deal it belongs to
({"opportunity": "acme-renewal", "stage": "technical-eval"}) and later pulls every meeting on that
deal with one query, instead of guessing from titles and dates.
- Address a meeting by its row id (#1547, @DmitriyG228).
/meetings/{id}/…and
POST /meetings/{id}/sharebeside the existing platform/native pair. GET /recordingsis a page, newest first (#1547, @DmitriyG228).limitandoffseton a list
shape.- Zoom served under a hosted or vanity hostname is recognised (#1547, @DmitriyG228).
Security
- The gateway strips authority headers by family, not by name (#1456, @DmitriyG228). At 0.12.26
the strip was an eight-name list ofx-user-*spellings, so a public client could send
x-internal-secret— the value published indocker-compose.yml— and be believed by the internal
tier. Any header beginningx-user-,x-internal-orx-vexa-internal-, plusx-admin-api-key
andx-gateway-verified, is now dropped from every client request. POST /botsno longer hands back the webhook signing secret it just stored (#1547,
@DmitriyG228). The response was a verbatim copy of the stored row, so the secret travelled
through the public gateway into caller logs and agent context. Rotate any webhook secret minted by
an earlier version.- Boot refuses the published placeholder secrets (#1456, @DmitriyG228). See Upgrade notes.
- agent-api validates the workspace repository host and pins git transports (#1539,
@DmitriyG228).
Deployment
- The agent surface is unchanged (#1553, @DmitriyG228).
/agent/*keeps the seven routes it
served at 0.12.26, now declared incore/agent/routes.v1.jsonlike every other domain, with the
compose default restored. A deployment that leavesAGENT_API_URLunset serves no agent surface
and answers404there — at 0.12.26 the same state answered403. - Dependency floors across the Python, pnpm and transcript-rendering lockfiles (#1541, #1542,
#1544, @DmitriyG228). - The ASWF 2020 v2 CCLA is accepted as an alternative corporate instrument (#1378,
@DmitriyG228). A company whose legal has already approved that shape does not need a bespoke
one. - Carve manifest for the v0.12.26 train (#1418, @DmitriyG228).
Fixed
- Asking for a Zoom bot through MCP could never work (#1631, @DmitriyG228).
request_meeting_botdropped the join URL for Zoom, and meeting-api refuses a Zoom request
without one — every attempt came back422. Meet, Teams and Jitsi were unaffected. /.well-known/mcp-tools.jsonwas served offline and absent live (@DmitriyG228) — the module's
entrypoint guard sat above the route, so nothing below it ran in the process every deployment
starts.- A person's settings move to identity, so one answer serves every service (#1456, @DmitriyG228) —
see Upgrade notes. - One absence is said once, however many meetings ran into it (#1547, @DmitriyG228) — a deployment
that does not run the agent domain no longer adds an identical queue item per completed meeting. - The friction sink never loses a report to a vocabulary word, and never to a missing one
(@DmitriyG228). GET /reactionsanswered500for every authenticated caller: the route shadowed the helper it
called (@DmitriyG228).
Known limit — three things this release does not finish. The person-settings import is
operator-triggered: settings move from the workspace file to identity, and an existing
deployment that does not run the one-shot migration starts everyone on the defaults (mail on,
clock in UTC) rather than on what they had. The agent-half flow tests are skipped when the agent
module is absent, which is the shape OSS ships — those flows (meeting_prep,email_chat, the
desk pair) are not registered at all without an agent domain, so their suites prove nothing here
and are marked present-only. And Python dependency licences are still unasserted: the licence
gate resolves the npm tree,pip-licensesscanning is owed rather than done (ADR-0009 §
Python licence scanning), and the per-release SPDX carries those fields asNOASSERTION
alongside the Ubuntu packages the Lite final stage installs. The gate is green on an inventory
that does not actually assert those terms.
Upgrade notes (breaking)
Read these before upgrading a self-hosted deployment. Each one changes behaviour that a 0.12.26
deployment relies on.
- Generate a real
INTERNAL_API_SECRET. Boot now refuses the literals this repo has published
(vexa-internal-secret,lite-internal-secret,changeme, …) and stops naming the variable. Use
openssl rand -hex 32and set the same value on every service that talks to another. Vexa Lite
mints a random one per boot and needs nothing set.VEXA_FLOWS_API_KEYhas no default at all and
refuses the same placeholders — flows-api will not start without it. - Upgrade the gateway with, or before, the services behind it. The authority-header strip is now
a prefix family rule. Anything that was reaching an internal tier by sending its own
x-internal-*header through the public edge stops working — which is the point. - MCP tool errors changed shape. A refusal arrives as
reason/message/action_url/
body, not as one sentence. Anything scraping the old text breaks.noticesis a new key on five
tools' results. /agent/*answers404, not403, whenAGENT_API_URLis unset.- Run the person-settings import. Timezone and the mail switches are read from admin-api, not
from.settings.jsonin the workspace. Without the operator-triggered migration everyone reverts
to defaults — anyone who had mail off starts receiving it again, in UTC. FLOWS_API_URL→VEXA_FLOWS_API_URLon admin-api. The old name is honoured for one release;
the new one wins when both are set.X-Flows-Admin-Key→X-Flows-Operator-Key. Accepted for one release, with a warning once
per process. The old name read as admin-api's token, which it never was.PROC_PENDING_GRACE_SECis removed. No longer read; delete it from your.env.- Copilot processed notes are no longer persisted after the bot stops. The durable notes pane
and the schedule digest'snotesflag stay empty once a meeting ends. - Rotate any webhook signing secret minted before this release — earlier
POST /botsresponses
returned it in the clear. - Email lookups on the admin API are case-insensitive. Reconcile existing case-variant
duplicate accounts before upgrading; which one resolves is otherwise plan-dependent. flowsexpects a database that has never held its tables. The engine creates them with
CREATE … IF NOT EXISTSand ships no migration runner.
Images (eleven — vexaai/v012-flows joins the release set with this version): vexaai/v012-admin-api@sha256:4c702354384eafe3a933cd537a106b7c15067cfd368a5f6da1a6e675e7e03e04, vexaai/v012-runtime@sha256:a1f6448fbb380b9433364e8b572ec25a12aa4f274bf403f1d83a89cbf5812f2d, vexaai/v012-agent-worker@sha256:a1120b24765ff6b1c86c5f9ddee35b5e71fdbb9543a23b918b278373ad705022, vexaai/v012-agent-api@sha256:6eb37574b33aab233aabbe5907e06e106bae44a403e5df781a436da8201a928d, vexaai/v012-meeting-api@sha256:4e9c201f656788755458fd6f2b9fbf3bfe0722d5b17d4cebd82b874b5aee98e1, vexaai/v012-gateway@sha256:2e76319812f28adbb6bc328ac0a7484d3826924a9d38a9dd9599dd190fbb0f19, vexaai/v012-mcp@sha256:4b2dbc08023ff424f40453d61a8f4d38a72de7eba1e5aaad1e428c309f63d338, vexaai/v012-terminal@sha256:8293350ff70008674032de67c682091eb8b87110412d2a4a1f3c4ed10cb1b09f, vexaai/vexa-bot@sha256:423c487aadb81514c71c9786758a3a730acbf5495e9593930da50c7442f158c1, vexaai/vexa-lite@sha256:945628e54d843cf6286a823ca8e226f2b3c48eb948ad2f895e64eb867b7a0d55, vexaai/v012-flows@sha256:e040c0de48924e78440e9cc4e71ed986471f101f99fbe5a5bd091af7e59996fa, published from the reviewed candidate packet
releases/v0.12.27/candidate-images.json and validated by run 34059966981.
In production: this release is what vexa.ai runs. Pinned on the internal channel — the witnessed
packet as entry 35, and entry 36 carrying the same eleven OSS digests plus the release-version pin —
and re-verified against the cluster after each pin. All four platforms were walked live on the
witnessed packet before the tag was cut.