-
v4.0.0
StableAll checks were successfulCI / Frontend tests, audit, and build (push) Successful in 1m17sCI / Backend lint (ruff) (push) Successful in 37sCI / Summarisation accuracy eval harness (stub provider) (push) Successful in 1m16sCI / Docker image build (push) Successful in 17sRelease / Create Forgejo release (push) Successful in 18sCI / Bot/backend version sync (push) Successful in 40sCI / Bot tests and audit (push) Successful in 2m2sRelease / Build and push versioned images (push) Successful in 2m44sCI / Backend migration, tests, and audit (push) Successful in 9m18sreleased this
2026-08-28 23:56:11 +00:00 | 412 commits to main since this releaseThe summary is no longer something the model asserts. It is composed from events
that have each been checked against the transcript in code — so chronology is a
sorted()and attribution is a lookup, not a model behaviour.Six migrations (
a2b3c4d5e6f8,b3c4d5e6f7a9,c4d5e6f7a8b0,
d5e6f7a8b0c1,e6f7a8b0c1d2,f7a8b0c1d2e3), applied in order by the
migrateservice. All additive. Only one backfills:c4d5e6f7a8b0copies each
member's existing character into the newcampaign_characterstable and leaves
the old columns in place, so a downgrade is twoDROP TABLEs and no data is at
risk. Take a snapshot before upgrading anyway — six at once is more than
usual, and the automatic pre-migration backup only helps if you know it is there.Rehearsed against a copy of production data before release: all six apply
cleanly, row counts unchanged, backfill exact.BOT_CONTRACT_VERSIONstays 1, so you can upgrade one image at a time.
BOT_EXPECTED_APP_VERSIONmoves to 4.0.0, which only logs a warning on
mismatch. Upgrading both together is still recommended.If you run with VAD disabled, transcription request volume changes. That
path used to send every speaker's track in one/transcribe/sessionrequest;
it now sends one request per speaker to/transcribe, the same endpoint the
VAD path already uses. Attribution can no longer depend on a label the server
echoes back — see below. VAD is on by default, so most installs are unaffected.Nothing is backfilled into the new structure. Transcript segment rows,
beats and summarisation runs exist only for sessions recorded after upgrading.
Old sessions keep working; they simply have no provenance to show, and a
plausible reconstruction would look authoritative without being so.Added
- Structured, verified session summaries. Extraction emits timestamped
events with cited transcript lines, constrained by a decoding grammar where
the provider supports one. A pure-code validator then checks every citation
resolves, every claimed actor actually spoke a cited line (or was named by the
GM in one), and every event's time range agrees with its own evidence. Only
events that pass reach the prose. The check costs nothing and behaves
identically on a 9B local model and a frontier hosted one, which is what keeps
the accuracy floor independent of model size. - The events behind a summary are kept and readable.
GET /api/sessions/{id}/summarisation-runs(GM only) returns each run with its
beats, their cited lines resolved to text, which failed validation and why,
what the validator repaired, and timestamp anchors back into the transcript. A
reprocess appends a run rather than replacing one, so two summaries of the same
audio can be compared. - Transcript segment rows. One row per utterance, with the track owner on it.
Member erasure, lore chunking and per-speaker QA read these instead of
re-parsing the rendered text, so the display format stops being a data
contract.GET /api/sessions/{id}/transcript-qa/speakersreports per-speaker
segment counts and spoken duration. - A player can run several characters (
campaign_characters), with a
per-session override so correcting a mid-campaign swap does not relabel old
recordings when they are reprocessed. - A per-campaign spoken language. Unset means detect, which stays the
default — a default of English would silently mistranscribe every non-English
table until someone found the setting. - A declared context window per provider, set in Admin → Bot Settings, used
to size every prompt in the pipeline. - A golden-transcript eval harness (
python -m evals) scoring coverage,
attribution, chronology and validation rate, with--repeatbecause one run
of a sampled pipeline is not a measurement. - Admin → summarisation mode, a one-release escape hatch back to the
pre-4.0.0 prose path if the verified one misbehaves on your data. It defaults
to the verified path and logs a warning on every session summarised without
checks.
Fixed
- A session that ran long on the clock but short on talking was thrown away.
Every track is padded with silence to the full length of the recording, and
nothing stops a recording when the voice channel empties — so a table that
played for two hours and forgot to stop for another fifty produced a complete,
correctly attributed transcript that the coverage check then rejected for
covering "only" 58% of the recording. It was measuring speech against wall
clock, which is not a coverage measure of anything. Worse, it ran after
transcription, so the work was already done, and it failed identically on
every retry while blaming the capture clock. Coverage is now measured against
how far into the session the tracks actually carry audible speech. - A second recording destroyed the first one's work. A GM who stopped at the
break and started again overwrote the transcript, any edits made to it, every
curated highlight, and the source audio — so no reprocess could recover it.
Intake now refuses with a clear message, curated highlights survive a
reprocess, and a second take is stored beside the first instead of over it. - A quiet player could be deleted from a full-length session. The silence
guard sampled 32 seconds of a 77-minute track, so it only reliably detected a
speaker whose audio contained one unbroken stretch longer than ~145 seconds. A
player who says a few words every several minutes fell between the probes and
their entire track was dropped before transcription, with the pipeline
reporting success. Detection is now an exhaustive scan, and a speaker dropped
by the backend is reported to the GM as present-but-uncaptured rather than
vanishing into a log. - The transcription server's echoed speaker label could silently relabel a
whole track. Attribution is now taken from the file that was sent and never
from the response, on every path. - Ollama installs summarised a fraction of each session.
num_ctxwas only
set when a caller happened to pass a window, and almost none did, so requests
fell back to Ollama's 2-4k default and were silently truncated. Every request
now declares one. - A summary cut short by the model's output limit looked complete. It is
still returned — half a summary a GM can read and correct beats none — but the
truncation is now recorded and surfaced. - Constrained JSON output was doing nothing on llama.cpp. The documented
request shape is silently ignored by real servers; the working form is now sent
alongside it, and a response that needed repair is recorded as degraded. - Structured extraction sampled at random, so summary coverage varied up to
eightfold run to run on the same recording. It is greedy-decoded now. - Compose silently discarded about a quarter of the verified events it was
given. Missing events are detected and re-requested by name. - Quotes on the quote board carried a speaker and timestamp the model
asserted; both now come from the utterance the words were found in. - Four prompts grew without limit —
/askin particular assembled every
approved lore entry at full length, forever. - Erasing a member matched their name in the transcript text. It now works
from the track owner recorded per utterance. - A bare Discord ID could appear as a speaker name and be read as a person.
Changed
- Speaker resolution happens entirely in code before the model sees anything.
The prompt no longer asks it to rename players to characters or to guess which
GM lines were an NPC. - Long transcripts are summarised in ordered windows sized from the declared
context window, rather than sent whole and hoped for. - Same-speaker runs are merged before prompting, cutting prompt size
substantially without losing attribution. - Overlapping speech renders as a time range, but only on the lines that
actually overlap. - "No LLM configured" now behaves the same way everywhere instead of eight
different ways.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Structured, verified session summaries. Extraction emits timestamped