Repository navigation
Releases
v0.12.28 — complete Zoom transcripts; Lite and Compose on versitygw
v0.12.28
Zoom
- Complete transcripts on busy calls (#1781). Every Zoom turn is captured in full, even when
the bot's page is under heavy load, and each turn is finalized from all of its audio. - Precise timing (#1781, #1778). Segments are timed from the captured audio on one shared
clock. Short pauses no longer split a turn, and long turns break at a natural pause. - A steadier live view (#1778). The live tail always shows the latest draft.
Install
- Recordings in versitygw (#1760). Lite and Compose now store recordings in versitygw:
Apache-2.0, pinned by digest, plain files in a local volume. Thanks to @infocillasas (#1671). - Your own S3 on Helm (#1760). Set
storage.s3.endpoint,storage.s3.bucketand
storage.s3.existingSecret. - Zero-edit setup (#1760).
make allgenerates its own internal keys; any value you set is kept.
Upgrading
- Lite and Compose: run
make migrate-storageto copy existing recordings; it verifies every
object and keeps the old volume. On Compose, setMINIO_ENDPOINT=storage:9000in.env(and
IMAGE_TAG=v0.12.28if you pin it). Guide - Helm: copy your objects to your bucket and set
storage.s3before upgrading.
Guide
Images (eleven). Seven are the v0.12.27 bytes, unchanged: vexaai/v012-admin-api@sha256:4c702354384eafe3a933cd537a106b7c15067cfd368a5f6da1a6e675e7e03e04, vexaai/v012-runtime@sha256:a1f6448fbb380b9433364e8b572ec25a12aa4f274bf403f1d83a89cbf5812f2d, vexaai/v012-gateway@sha256:2e76319812f28adbb6bc328ac0a7484d3826924a9d38a9dd9599dd190fbb0f19, vexaai/v012-mcp@sha256:4b2dbc08023ff424f40453d61a8f4d38a72de7eba1e5aaad1e428c309f63d338, vexaai/v012-agent-api@sha256:6eb37574b33aab233aabbe5907e06e106bae44a403e5df781a436da8201a928d, vexaai/v012-agent-worker@sha256:a1120b24765ff6b1c86c5f9ddee35b5e71fdbb9543a23b918b278373ad705022, vexaai/v012-flows@sha256:e040c0de48924e78440e9cc4e71ed986471f101f99fbe5a5bd091af7e59996fa. Rebuilt: vexaai/v012-meeting-api@sha256:cca46266feedcff29c3478472fd40133ba51f3c49a489a290040a56f1f0f3dca, vexaai/v012-terminal@sha256:d65f81c284553e98e3ee2db4475ee6deea8691f0bd95ccf064e4a99c564095d3, vexaai/vexa-lite@sha256:9eebb534fdab4079baf11af678968ac8d9679e20ce48db206bcf5a62b85eb538. Bot: vexaai/vexa-bot@sha256:e2398bfac7fa8671c209e996bc6451598b58438c851956ec050a0300f5a5354b. Published from the reviewed candidate packet releases/v0.12.28/candidate-images.json and validated by run 37914860589.
In production: vexa.ai has run this bot since 8 October, and its admin-api, gateway, runtime and
MCP are the same bytes as this release.
v0.13.1 — the minutes line (alpha)
v0.13.1 — the minutes line (alpha)
The chat is the interface to your meetings. Ask a question and the agent answers from your own
transcripts, documents and calendar. One emailed link signs you in and opens the question already
composed. Flows react to meeting events — a prepare message before, minutes after — and you compose a
flow from named steps without a rebuild. Agents reach your meetings over authenticated MCP with
scoped, delegated tokens. Workspaces hold the documents the agent reads and writes.
New tiers in the Helm chart: agent-api, terminal, flows, and a workspaces volume.
Since v0.13.0: a bot requested inside a workspace is the workspace's meeting — live for every
member, not only the person who asked it in (#1648, delivered by #1650). vexaai/v012-terminal now
ships the minutes variant, the same bundle the pilot was shown; both identity legs assert
ai.vexa.terminal.mode=minutes on the published image, on amd64 and arm64. Base-image and chart
pulls in the release workflows no longer read Docker Hub (#715); every identity and alias read still
does, because those verify our own published bytes.
Alpha. This build has not been through a witness pass and is published as a candidate for
evaluation, not for production.
Not in this build: deletion and retention controls; runtime-editable agent prompts; the shipped
MCP does not yet verify delegation tokens.
Images
Published from candidate v0.13.1-alpha.2 (build run 34096619850 at 036911efa) and aliased to
v0.13.1 without a rebuild: eleven images, 21 platform identities — the bot is amd64-only.
The packet is releases/v0.13.1/candidate-images.json, validated against the published bytes by
run 34104704881 with every leg green.
v0.12.27 — the full MCP, flows, and searchable meetings
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. - **
flowsexpect...
v0.12.26
v0.12.26
Zoom
- Per-participant speaker attribution (#1018, @jbschooley). Zoom transcripts are named per participant with a self-correcting vote, replacing the mixed-lane guess. Maintainer hardening on top: the deaf-capture guard restored on the per-track branch, typed observations on the Zoom lane, and the page-side name resolver extracted into a tested module with a Speaker A/B fallback and a self-exclusion backstop.
- Better admission (@jbschooley). Browser-signal hardening lets the bot into Zoom meetings that previously refused it.
- In-meeting popups are dismissed during the call (@jbschooley) — the mic advisory, the AI-Companion notice and the hardware-acceleration toast no longer sit over the meeting or the recording.
Known limit — the Zoom bot joins as a web client, so not every Zoom meeting can be recorded. Some Zoom accounts and meetings block automated browser clients outright; the bot is refused at the door there, ends as
failedwith reasonzoom_requires_rtms, and no transcript is produced. This is a platform policy on the host's side, not a fault in the bot — those meetings need Zoom's Realtime Media Streams path, which this release does not ship (#706). Meetings that do admit a web client are unaffected.
Meetings
- Join failures name themselves (#1075). A failed join now records a typed reason, the stage it failed at, whether the cause was ours or the platform's, and how long it took — instead of one opaque
failed. Visible on the meeting record and through the API. - Text-only custom STT responses reach the transcript (#1148). OpenAI-compatible endpoints that return only
textno longer have their output discarded for missing timestamps. - Voxtral / JSON-only transcription backends (#1342, @Cantfirmed).
Deployment
- Noble base image for the bot, lite and full (#1011, @jbschooley).
DEFAULT_BOT_NAMEis honoured without rebuilding the terminal (#1258).
Fixed
- The probe's live-view listener re-sends its subscribe until acked (#1354) — it raced meeting-row creation and a refused subscribe was permanent.
- The full bot image builds again on the new base:
awscliis no longer in the noble archive, so AWS CLI v2 installs from the vendor package.
Images: vexaai/vexa-lite:v0.12.26 and the compose set (v012-runtime, v012-meeting-api, v012-gateway, v012-admin-api, v012-agent-api, v012-agent-worker, v012-mcp, v012-terminal, vexa-bot), published from the reviewed candidate packet releases/v0.12.26/candidate-images.json and validated by run 33421145816.
In production: this release is what vexa.ai runs, pinned as channel entry 0.12.23-estate-20260829-seq12 on 2026-08-31 and re-verified against the cluster after the pin.
v0.12.25
v0.12.25
Maintenance release: everything merged since v0.12.23 (v0.12.24's lite/compose images were never published — its tag predated the release-commit convention; this release closes that gap too).
Added
- Meeting participants read —
GET /meetings/{platform}/{native_meeting_id}/participants, rows labelledinvite/speaker, owner-scoped, with an honestobserved_roster: "not_recorded"marker (#451). - Teams passcode joins — numeric Teams id +
passcodenow builds the real…/meet/<id>?p=<passcode>URL; unknown aliases are a typed422; the passcode stays out of stored URLs and logs (#892). - Sized spawned pods — bot and agent-worker pods declare CPU/memory requests + limits via
runtime.workloadResources, so quota-controlled namespaces admit them (#1005). - MCP
report_issue— an agent can file a ticket from inside its session, bound to the cluster's record of the same meeting (#1272).
Changed
- One API home for interactive acts —
POST …/actsper ADR-0035; speak/chat kept as aliases; screen/avatar retire fromapi.v1; the status page stops overstating speak (#1089). - Meetings list ordered by event time, active meetings pinned to the top (#1226).
Fixed
- Capture-fault ≠ empty room — a bot whose audio capture breaks mid-meeting no longer ends a live meeting as
completed(left_alone); it logs a suspected capture fault and attempts a restart (#1192). - Restart mid-leave no longer strands meetings in "stopping" (#1280).
- Text-only STT hardening — the transcription client tolerates JSON-only/Voxtral-style engines (#1149, #1343).
- Terminal honors
DEFAULT_BOT_NAMEwithout a rebuild (#1259). - Gate failures print their diagnostic — empty stdout Buffer no longer swallows the real error at 21 call sites (#1107).
Delivery evidence
Validated at station t25-01225 (vexa-stations: battery, row-0 image-ID proofs, live-meeting run, five promise-exercises, full-green lite probe S0–S8). Production runs this train as channel entry 0.12.23-estate-20260829-seq11 (cosign-signed, verifier verdict ELIGIBLE).
v0.12.24
Delivered to hosted production 2026-08-29 (channel entry 0.12.23-estate-20260829-seq10), station-validated on live meetings before release.
Fixed
- Teams bots join with a separate
passcode(#892, #1201). A numeric meeting ID + passcode now builds the join URL Teams actually uses; the passcode never appears in any log line. Proven live against a passcode-required meeting. - A broken audio capture no longer ends a live meeting as
completed(left_alone)(#1192, #1195). Connected streams delivering nothing are treated as a capture fault: the bot holds the meeting, logs it, and attempts one capture restart. On platforms that keep a server-side mix alive (Teams), affected runs now end at themaxActiveMsceiling rather than reporting a false success. - Restarting mid-leave no longer strands a meeting in "stopping" (#1280).
- Every gate failure prints its diagnostic (#1107, #1114) — an empty stdout Buffer was swallowing the real error at all 21 call sites.
Added
- Delete a completed meeting's artifacts (#116, #1293). Owners erase transcripts and recording artifacts from primary stores; the meeting row remains as a tombstoned usage record. Active meetings answer
409. This is what makes the published privacy promise true. GET /meetings/{platform}/{native_meeting_id}/participants(#451, #1275) — who was in a meeting, each row labelled bysource(invite/speaker), withobserved_roster: "not_recorded"stating honestly what the core does not know.- Spawned bot and agent-worker Pods declare CPU/memory requests and limits (#1005, #1093) — quota-controlled and
ResourceQuota/LimitRange namespaces now admit dynamically created Pods; sizing is configurable per profile. - MCP
report_issue(#1272) — an agent can file a ticket from inside its own session; off untilVEXA_TICKET_SINK_URLis set. - Interactive capabilities get one API home (#1089, ADR-0035) —
POST /bots/{platform}/{native_meeting_id}/acts, with speak/chat kept as aliases.
Changed
GET /meetingsorders by event time (#1226) — non-terminal meetings pinned on top; calendar-imported meetings sort where they happened, not where they were created.- The minio-init Job is pinnable and sized (#1321) — its client image is a value (pin by digest for reproducibility); the Job declares its own resources.
Docs
- The meeting completion-reason taxonomy — all ten values, who owns each, and what a
failedrow does and does not tell you (#1284). Operability numbers with the commands that reproduce them (#1285). API-key provenance on the pages that carry snippets (#1301, #1302). "Air-gapped" corrected to self-hosted across the comparison pages (#1304). The MCP page linked from where agents actually look (#1345). 0.10 → 0.12 upgrade runbook (#1332).
v0.12.23 — Calendar auto-join discipline, and Teams CSRC transcription
v0.12.23 — Calendar auto-join discipline, and Teams CSRC transcription
Calendar-dispatched bots now follow a fixed, observable contract: dispatch at
start−2 min, wait up to 15 min in the lobby, retry genuine failures once per bounded
backoff inside the occurrence window, never re-arm a served or stopped meeting,
record under the same flag as manually started bots, and honour an explicit stop on
every path. Teams transcription routes the mixed audio into per-speaker windows from
the RTP transport's contributing sources; a speaker name stays provisional until the
roster has settled.
Calendar & scheduled bots
- Multiple calendar feeds, managed from the dashboard: named, independently synced
ICS connections; meetings deduplicate across feeds and keep every source visible
(#1150). - Bots dispatch two minutes before the scheduled start and wait up to fifteen
minutes to be admitted (#1210). - A failed auto-join retries on a bounded backoff, and the reason for the wait is
readable on the meeting (#1200). - A meeting already served or stopped is never re-armed; only genuine failures
retry, and only within their own occurrence window
(#1211). - An explicit stop is stop: stopping a scheduled meeting cancels it before
dispatch, a stop that races a starting bot leaves nothing running, and the stop
is recorded as the outcome (#1212). - Calendar auto-joined meetings are recorded like manually started ones
(#1217).
Teams
- CSRC transport activity routes the mixed audio into per-speaker transcription
windows with the same buffering contract as Google Meet
(#499 — attribution work; the
exactly-once oracle that issue asks for stays open). - A speaker name earned before the participant list is complete stays provisional
and is corrected as evidence accumulates — no more locking one person's words
under another's name (#1228). - Names resolve from Teams' stable
data-tidattributes instead of hashed CSS
classes; only themainAudiomix is transcribed, and double-mirrored tracks are
deduplicated (#1024).
Correctness & API surface
- One bot per meeting, even under concurrent requests; a request for a live
meeting adopts the running bot (#1185). - Self-hosted deployments fail closed when that guarantee cannot be enforced: the
admin service refuses to start without the unique index
(#1187). - API keys only do what their scope allows — scope is enforced on bot lifecycle
and user-config routes (#1236). - Meeting responses ship owner-private fields (webhook URL, delivery config) to
the owner and nothing sensitive to a transcript-share viewer
(#1243,
#1244). - Operator plumbing: admin identity by authoritative ID (#994), producer-observed
service provenance on completed meetings (#984), optional external service
authority (#988), signed system callback for terminal meeting facts (#992).
Infrastructure
- Every deploy surface — Lite, Compose, Helm — now runs
Valkey 8.1.9 instead of Redis
(#653). - Release promotion copies registry manifest bytes exactly and reads back every
frozen descriptor; nothing is rebuilt after the witness (#951).
Docs
New guides and honest-status pages: n8n (#1076), the
MCP server boundary (#1077), share-links and
interactive-bots status (#1078, #1086), hosted-runs-0.12 start-here (#954),
signal-driven redirects (#1111), and FunASR validation attributed to the upstream
maintainers who ran it (#928).
Getting it
Images are published multi-arch (linux/amd64 + linux/arm64) under
:v0.12.23, byte-identical to the witnessed release candidate
(v0.12.23-rc.21). Lite and Docker Compose build and run green at this source.
Upgrade notes
- Self-hosted with an existing database: the admin service now refuses to
start if the one-bot-per-meeting unique index is missing (#1187). Apply the
shipped schema migration before rolling this version; a fresh install needs
nothing. - The Redis→Valkey swap is transparent for stock deployments; custom images or
pinnedredis:*references need the new image name (#653). - The declared
api.v1surface has not been re-frozen for the routes added since
0.12.18; the reconciliation is tracked in
#1234.
Not claimed
- A transport-contested phrase on Teams is currently published under both
contributing tracks — detected and counted in pipeline health, not yet
resolved or marked in the public transcript (#499, documented in-repo as
deliberately unresolved). - Teams naming under heavy participant churn can still flap before settling;
hysteresis and a fully deterministic CSRC→name path are tracked (#1229, #1230). - Jitsi: the alone-leave timer and admitted→active lag have known residuals (#1214).
- A stop arriving before the meeting row exists is lost rather than remembered (#1213).
- Zoom: not updated in this release.
Credits
Jacob Schooley (@jbschooley) — Teams speaker-name
resolution from stable data-tid, mainAudio-only transcription and track
dedup, from #1024.
The FunASR maintainers (@LauraGPT) — upstream
validation of the FunASR/SenseVoice custom-STT path
(#928).
v0.12.22 — Teams speaker attribution
v0.12.22 — Teams speaker attribution
Teams turns now carry the speaker's name. The bot reads who is audible from the
WebRTC transport's contributing sources, correlates that against the roster panel
and tile names, and attributes at word granularity rather than per turn.
Teams
- Speaker names resolve from stable DOM attributes, guarded so a control label, a
clock or a machine token can never become a name
(#1119). - The audible-source signal comes from the RTP contributing-source list, so
attribution follows the transport rather than the UI's animation, and stream
selection is verified against voice energy. - Names are applied at word granularity. A name another track has already earned
is treated as contamination, not as disagreement. - Only the
mainAudiomix is transcribed; double-mirrored tracks are deduplicated,
and a mix that is present but carries no sound is abandoned rather than
transcribed as silence. - Unattributed speech is the number we are driving to zero. This release cuts it
substantially and publishes what is left with an empty speaker rather than a
guess, so the remaining gap stays visible release over release.
Transcript quality
- Superseded pending drafts are retracted, and a confirmed turn's dangling tail is
promoted on close, so dedup no longer loses speech. - Invented media-artifact text is suppressed, and every suppression is reported.
- Overlap trim, gap reclaim, and a four-second cut on long turns.
Getting it
Images are published multi-arch (linux/amd64 + linux/arm64) under :v0.12.22.
Lite and Docker Compose deployments build and run green at this tag.
Not claimed
- Zoom: not updated in this release — next target.
- Roughly 4–7% of rows still publish unnamed under heavy crosstalk.
Credits
Jacob Schooley (@jbschooley) — transcript
retract/promote (6eff8c09, ba382219) and mainAudio dedup (dc740e52), from
#1024; also 2db27364.
Daniel Dormann (@danieldormann) — structural
Teams name resolver (0d0bdbda), from
#1121, and the report that opened
#1119.
v0.12.18 — dependable joins and truthful operator failures
v0.12.18 — dependable joins and truthful operator failures
v0.12.18 tightens the paths that decide whether a meeting bot really joined,
is still alive, and failed for a reason an operator can act on. It also makes
the MCP front door stream correctly, fixes Lite startup, and removes several
false-success states from bot creation and deployment.
Meeting reliability
- Google Meet lobby bots are no longer reaped at five minutes while still
legitimately waiting for admission (#862). - Meet host denial wins over ambient reCAPTCHA and terminates with a typed,
non-retryable reason (#840). - The browser locale is deterministic (
en-USby default), with its resolved
language recorded for diagnosis (#856). The English lobby path was walked
live; arbitrary non-English joining is not claimed. - Teams no longer treats generic alert UI as evidence that the bot was
removed; the staged bot stayed active for 207 seconds under the live removal
monitor (#600). - Mixed-lane turns without a resolved participant name now use the stable
Speakerlabel instead of leaking the internalseg_Ncluster identifier
(#890). Human name binding remains a later attribution-quality step. - Teams authentication redirects and non-admitted bot deaths now preserve
their real causes instead of degrading into admission timeouts or
reason: None(#915, #926). These external failure shapes are
machine-proven; neither recurred during the release witness. - Stop during pre-admission now reaches the phase-aware withdraw path and
cleans up the workload (#889). Meet's host-side knock can still persist;
that distinct visible withdrawal gap remains
#839.
Truthful APIs and operator behavior
- A bot workload that never starts no longer yields false
201;POST /bots
returns502with the cause and marks the meeting failed immediately (#718). - Invalid native meeting IDs containing URL/query characters fail at intake
with422; callers should usemeeting_urlandpasscode(#892). - Meeting API remains available for durable reads during Redis loss and
reports Redis degradation on/health; Redis-dependent Stop fails narrowly
and retryably (#809). - Redis keyspace reconciliation is no longer on the ten-second collector hot
loop, removing the known/healthsaturation amplifier (#893). - The webhook introspection capture is bounded, removing its unbounded
in-process retention source (#803).
MCP, webhooks, and identity
- MCP streamable HTTP is available through the authenticated gateway
/mcp
front door. SessionedGET /mcpopens and keeps its SSE stream instead of
being buffered into a timeout (#795, #921). - Admin token mint honors JSON
scopes, keeps query compatibility, and refuses
unsupported body fields loudly (#922). - Core now records recent webhook delivery outcomes in a per-user,
secret-safe ledger exposed atGET /user/webhook/deliveries(#841 core
half). The dashboard does not display that ledger yet; the consumer remains
open in v0.12.20.
Deployment
- Helm migrations use the same pinned image tag as the deployed release
(#900). - Admin API retries transient database DNS/connect races with bounded backoff
(#901). - Vexa Lite pins all service environments to Python 3.12, restoring reliable
admin API startup and first-run key provisioning (#927). - The no-STT contract is documented truthfully: transcription is required by
default and missing configuration fails loudly; capture-only is an explicit
transcribe_enabled=falsechoice (#532).
Upgrading
From any v0.12.x release, pin IMAGE_TAG=v0.12.18. The guarded :v012
pointer moves only after the published image set, value gate, and release
witness are green. There is no database schema migration in this batch.
Behavior refinements to note:
- token JSON bodies reject unknown fields with
422; - native meeting IDs carrying URL characters reject with
422; - bot workload start failure now returns
502, not201; /mcpand/user/webhook/deliveriesare new gateway surfaces.
Artifact identity
The stable tags are same-byte aliases of frozen release candidates; they were
not rebuilt after validation. Production revision 103 and meetings 24740/24741
witnessed the stage2 Bot at
sha256:9442a44558fd48950208cbef40673cc7a0b2feb41f380964fc74a0e25bf18fae.
The public Bot below is the packet3 replacement: its runtime inputs are
tree-identical to the tagged source and its image/Compose validation passed,
but it was not deployed or human-witnessed on hosted stage or production.
| image | :v0.12.18 digest |
|---|---|
vexaai/v012-admin-api |
sha256:49a8bd29268250cb37976c3170fde41c960f00d55f31ef4a42e17062b968b114 |
vexaai/v012-runtime |
sha256:653fcd1a8edbc925a174a55fe34863d414c56c8ef367a94b449f89a5a3a5c4e1 |
vexaai/v012-agent-worker |
sha256:d4c637ca1ca9c05ff6de852778299da665374ed9d59115088aa030d90212bf15 |
vexaai/v012-agent-api |
sha256:af4f2bf2008ed2c910904e82155e06d3b0e747d04d7c7ccb9f14208b17c49bae |
vexaai/v012-meeting-api |
sha256:d1db225472d54f0b087e1b7a342c465b01b7e7f042372a730d3b32e0c6836f4a |
vexaai/v012-gateway |
sha256:4b9bd9d6d33f4f2ec8df0b9530b02942482c0399ec71f39722fb06f48aae55df |
vexaai/v012-mcp |
sha256:72158d83293ce48c1cfd5c9bd8e2762e72a2da63d962fdbdbf7fe9b09daa8755 |
vexaai/v012-terminal |
sha256:a668ad43e225364422b225939fad057bff33db8b5bb3d6c341e1b625c5bde008 |
vexaai/vexa-bot |
sha256:a7f8feae7870b722e3542fb7cb054ff7c092e62f4c5a6b6a3b63e52f8cd1fe47 |
vexaai/vexa-lite |
sha256:5d0b6f865afe726109bb326361b917a3d9f762d64abbeea0826744134162d051 |
Hosted production directly held the listed admin API, runtime, meeting API,
and gateway bytes. It held the stage2 Bot digest recorded above, not the
packet3 public Bot digest. Agent worker, agent API, MCP, Terminal, packet3 Bot,
and Lite are proven by published-candidate validation; they are not claimed as
hosted production workloads.
Known boundaries
- Host-visible graceful lobby withdrawal remains #839.
- Dashboard webhook Delivery History remains #841.
- Teams auth-redirect and Zoom non-admission reason fixes have exact-head
machine evidence but no live recurrence in the witness window. - Jitsi hosted meeting detail remains #937.
- Real audio→words is witnessed live; the standalone machine wav→words oracle
remains deferred. - Hosted transcription currently has a functional Cloudflare fallback, while
primary health remains false and direct BBB was not exercised. - Hosted Account/API-key management currently has a webapp-to-Admin-API
compatibility incident (#942,
platform carry vexa-platform#127).
It is not delivered or closed by this OSS release.
Credits
Contributors: Joseph Yaksich, Felix-Ayush, and Dmitry Grankin.
Reporter credit: Valerie Phoenix (#927).
Community PR validator: Dmitry Grankin.
Release witness: Dmitry Grankin, corroborated by dashboard, bot, meeting API,
Redis, Postgres, and Kubernetes evidence.
Delivery guarantees
| # | Guarantee | Evidence |
|---|---|---|
| 1 | Users pull the bytes that were proved | exact candidate map; stable alias digest equality |
| 2 | Compose, Lite, and Helm install paths work to their declared scope | base-eight published-candidate validation plus bounded packet3 Bot/Lite validation |
| 3 | Real audio produces speaker-attributed words | hosted stage and production human witness; standalone wav→words remains unclaimed |
| 4 | Fresh empty VM works | not claimed; no fresh-VM leg was run |
| 5 | Existing behavior remains covered | v0.10 compatibility, bot-spawn, Compose, Lite, Helm, and arm64 validation |
| 6 | Each image contains what its name promises | image-identity validation |
| 7 | A human witnessed the hosted value | stage2 hosted witness plus production meetings 24740/24741; packet3 Bot is validation-only |
| 8 | Every batch change has acceptance evidence | 21-PR value map plus inherited #890 and exact-head checks |
| 9 | Notes state coverage boundaries | this document |
| 10 | Moving pointers change only after validation | release value/witness gates plus the release-promote environment |
v0.12.16 — the community batch
The community batch — 7 PRs from 5 external contributors, plus the Teams live-transcript fixes and reliability work. Baseline v0.12.15.
Highlights
Google Meet & bots
- Leave-when-alone — a bot alone in a meeting now emits
left_aloneand leaves instead of burning capacity for up to 4h (#545, @m-tauqueer). Witnessed live on a real Google Meet. - Lite bot no longer echoes meeting audio back into the call (#819, @Ahmed-Tokyo). Witnessed live.
- Redis first-use connect race ("Socket already opened") fixed via idempotent memoized connect (#820, @Ahmed-Tokyo).
Teams / mixed lane
- Live transcripts render in real time — the mixed-lane mapper now stamps
absolute_start_time, so Teams/Zoom transcripts paint live on the dashboard instead of only after a reload (#895). - Stable speaker labels — unattributed turns show a single "Speaker" instead of a new fake speaker per line (#890).
Transcription
- Bring-your-own STT — any OpenAI-compatible endpoint, documented (#537, @LauraGPT).
- Degraded STT reports why on the terminal lifecycle event instead of completing silently (#552).
- Google Meet silence-hallucination suppression (ja/tr phrase lists + near-silent submit gate).
API & correctness
- Malformed
native_meeting_id→ typed 422 at intake, not an asyncpg 500 (#843). - Meeting timestamps serialize as UTC (
Z) so clients render local time correctly (#860, @jacobschooley). - Zoom URLs now accepted in the join dialog (#897); honest version surfacing on webapp + dashboard (#72).
Honest boundaries
left_alonedoes not yet fire on Zoom/Teams/Jitsi when the bot is alone from the start (readiness latches on first audio) — #866.- Community-contributed STT examples (FunASR/SenseVoice) are marked unwitnessed — #863.
- Dashboard browser logout has a known gap — #899. Mixed-lane transcript quality (oversplit/hallucination) is the focus of v0.12.17 (#847).
Contributors: Mohammad Tauqueer, LauraGPT, Ahmed Tokyo, Jacob Schooley, Dmitriy Grankin.
Full changelog: https://docs.vexa.ai/changelog · Integration PR: #867