Headline
The assistant stops making things up, by design.
v6.1.0 rebuilds the AI assistant on a retrieval-grounded foundation. Every answer must cite a row, a setting, or a policy document. Answers that can't cite become honest “I don't know” refusals instead of confident fiction. We also shipped calendar conflict warnings and an assistant-driven invoice triage flow.
Section 1
Highlights
Three threads in this release. AI grounding was the headline; the calendar work was the second-most operator-requested feature in our feedback survey; and the edge-function cold-start cut was the quiet win that operators won't notice directly but will feel in every dashboard load.
- Assistant grounding — every answer cites a source. Confidence gating cuts off low-evidence answers before they reach the user.
- Calendar conflict warnings— surfaces overlaps proactively as you draft a booking. Includes a one-click “resolve by shifting the new booking” option.
- Edge function cold-start cut — median cold start down 22% after deferring Sentry init for fast-path requests.
Section 2
AI assistant
The pre-6.1.0 assistant was a thin shim over an LLM. It was fast and friendly, and occasionally invented things — a non-existent member, a not-yet-shipped feature, a member tier that the tenant didn't have. The new assistant is retrieval-grounded from the ground up.
How grounding works
On every turn, the assistant first retrieves up to 8 candidate sources from the tenant's data: rows from members, bookings, invoices, and the tenant's operator-curated knowledge base. The candidate set is RLS-scoped, so the assistant cannot see rows the asker couldn't see directly. The model is then prompted to answer using only those sources or refuse.
// Refusal is a first-class response, not an error.
type GroundedAnswer =
| { kind: 'grounded'; text: string; citations: Citation[] }
| { kind: 'refused'; reason: 'no_evidence' | 'out_of_scope' }
async function answer(question: string, ctx: AskerContext): Promise<GroundedAnswer> {
const candidates = await retrieve(question, ctx, { limit: 8 })
if (candidates.length === 0) {
return { kind: 'refused', reason: 'no_evidence' }
}
const llmAnswer = await callModel({ question, candidates, ctx })
if (llmAnswer.confidence < 0.7) {
return { kind: 'refused', reason: 'no_evidence' }
}
return { kind: 'grounded', text: llmAnswer.text, citations: llmAnswer.citations }
}What “refused” looks like
When the assistant can't find evidence, the user sees a short, honest line: “I couldn't find anything that answers this. Want me to search a different way?” This is intentionally not a generic empty state — the assistant offers concrete alternatives based on partial matches.
Citations
Every claim is linked to a source. Clicking a citation opens a panel with the underlying row or document, scoped to the asker's permissions. Citations are retrievable via the API for downstream auditing — every assistant turn is logged with its citation set for 90 days.
Section 3
Calendar
Calendar conflict warnings were the most-requested feature in our Q4 2025 feedback survey. They're straightforward — when the slot you're drafting overlaps an existing booking, we tell you immediately, with a one-click resolution.
- Inline warning banner on the booking draft sheet, including a shortcut to view the overlap.
- Shift-to-fit button suggests the nearest non-overlapping slot (15 / 30 / 60 minute snap, configurable per tenant).
- Recurring booking conflicts are surfaced at draft-time, not at save-time. The list shows which occurrences are affected.
-- The conflict probe runs in the same tx as the draft save.
SELECT b.id, b.start_at, b.end_at, b.member_id
FROM public.bookings b
WHERE b.resource_id = $1
AND b.status IN ('confirmed','pending_payment')
AND tstzrange(b.start_at, b.end_at, '[)')
&& tstzrange($2::timestamptz, $3::timestamptz, '[)');Section 4
Performance
- Edge function cold-start: down 22% on the median (was 320ms, now 250ms). The win came from deferring Sentry init for fast-path requests.
- Member portal home: down 18% in JS bundle size after we moved the avatar generator behind a dynamic import.
- Assistant turn time: p50 down from 1,800ms to 1,250ms (~30%) after we parallelised retrieval with the model warmup.
- Booking list query: 11% faster on the median tenant after we added a covering index on
(resource_id, status, start_at).
Before v6.1.0 ~ 320ms After v6.1.0 ~ 250ms ⟶ -22% Notable: the p95 saw a smaller gain (-9%) because the deferred-Sentry win is most visible on requests that don't need the full SDK loaded.
Section 5
Fixes & polish
- Assistant follow-up loop — a multi-turn assistant conversation could rarely lose the tenant context between turns. The conversation context is now persisted server-side keyed by the conversation id, not the client session.
- Calendar drag-to-create on touch devices used to occasionally double-fire on rapid taps. Debounced at the pointer-event layer.
- Invoice PDF font subset— non-ASCII characters (café, résumé) used to render as blanks. Now embeds the Latin-1 + Latin-Ext subset by default.
- Member portal dark mode contrast — three components had insufficient contrast in dark mode. Failed our internal Axe audit. Fixed.
Section 6
Migration notes
One operator-facing note, one for integrators.
1. AI assistant feature flag
The new grounded assistant is on by default for all tenants. The previous, ungrounded version is removed — there is no fallback flag. If you had your own feature flag on for the old assistant, you can delete it; the new one is always on.
2. Webhook payload: turn objects
The assistant.turn.completed webhook payload gained two new fields: kind (grounded | refused) and citations (array of { type, id, snippet }). Old fields are unchanged. If you have a typed schema validator, expect the new fields to start arriving on 2026-03-15.
// assistant.turn.completed payload (shape, abbreviated)
{
"event": "assistant.turn.completed",
"turn_id": "turn_01HXY...",
"conversation_id": "conv_01HXY...",
"asker_user_id": "usr_01HXY...",
"kind": "grounded", // NEW
"citations": [ // NEW
{ "type": "booking", "id": "bkg_01...", "snippet": "..." }
],
"latency_ms": 1180
}Privacy note: Citation snippets are scoped to what the asker could already see. The webhook receiver can therefore include them in operator-facing audit dashboards without additional permission checks.
Found this useful?
Share the release notes with your team.
One short link per release, hosted publicly. No login wall.