-
v4.1.1
StableAll checks were successfulCI / Backend lint (ruff) (push) Successful in 27sCI / Bot/backend version sync (push) Successful in 32sCI / Summarisation accuracy eval harness (stub provider) (push) Successful in 1m13sRelease / Create Forgejo release (push) Successful in 13sCI / Frontend tests, audit, and build (push) Successful in 1m42sCI / Bot tests and audit (push) Successful in 1m50sCI / Docker image build (push) Successful in 4m41sCI / Backend migration, tests, and audit (push) Successful in 5m56sRelease / Build and push versioned images (push) Successful in 7m12sreleased this
2026-09-05 00:19:52 +00:00 | 2641 commits to main since this releaseA patch on the way to v4.2.0: the AI provider is named rather than guessed
from its URL, and a/askanswer that ran out of room now says so.No migrations. Nothing to rehearse, nothing to back out.
The bot contract is unchanged (still v1). The one
/api/bot/*change is
an optional field added to the/askresponse, which the bot tolerates when
absent, so backend and bot images can be upgraded independently in either
order.One new optional setting — the AI provider select in Admin → Bot Settings.
Nothing changes on upgrade unless you set it; see the entry below for who
should.Added
- [webapp] Admin → Bot Settings can now name the AI provider explicitly
instead of letting it be detected from the endpoint URL (#351). Detection
matches on the URL —anthropic.com,openai.com,:11434— and falls back
to llama.cpp for everything else, so if you run a gateway or reverse proxy
on your own domain, your endpoint has been treated as llama.cpp regardless of
what is behind it. The provider decides the JSON-schema format, how
truncation is detected, and the default context window, so a wrong guess
degrades summaries quietly rather than failing. Leave it on "Detect from the
URL" if that guess is already right for you — nothing changes on upgrade.
Fixed
/askno longer presents a cut-off answer as a complete one (#426). The
question-answering path never checked whether the model ran out of room, and
a truncated answer is prose that simply stops — there was nothing to notice.
It is now marked in the Discord embed. Two related fixes on the same path:
the output limit was 1024 tokens on Anthropic and OpenAI (small for an answer
drawn from ten sessions plus the wiki) and unset on llama.cpp, and is now
4096 everywhere; and on Ollama the question was being silently cut to
that provider's 2–4k default, so the model answered from a fraction of the
context it was supposed to have. If your/askanswers have felt oddly
narrow on a self-hosted Ollama, that is why.- [webapp] A silently truncated prompt is now reported on every LLM provider,
not just Ollama (#351). The check that warns when a provider consumed far
fewer prompt tokens than were sent existed, but was wired to a single code
path. llama.cpp, Anthropic and OpenAI deployments got no warning at all, so a
transcript that overflowed the context window could be summarised from its
tail with nothing in the logs to say so. If you self-host llama.cpp and see
this warning appear after upgrading, your configured context window is too
large — see the note under "Context window (tokens)" in Bot Settings.
The check stays quiet on Anthropic and OpenAI, where prefix caching makes a
low count ambiguous rather than alarming.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [webapp] Admin → Bot Settings can now name the AI provider explicitly
-
v4.1.0
StableAll checks were successfulCI / Backend lint (ruff) (push) Successful in 46sCI / Docker image build (push) Successful in 9sRelease / Create Forgejo release (push) Successful in 12sCI / Bot/backend version sync (push) Successful in 41sCI / Summarisation accuracy eval harness (stub provider) (push) Successful in 1m42sRelease / Build and push versioned images (push) Successful in 54sCI / Frontend tests, audit, and build (push) Successful in 2m15sCI / Bot tests and audit (push) Successful in 2m32sCI / Backend migration, tests, and audit (push) Successful in 7m37sreleased this
2026-09-01 18:11:30 +00:00 | 2653 commits to main since this releaseData Durability & Recovery. Nothing you have made can be destroyed by a single
click, a crashed worker, or a background job you never saw run.⚠️ Two migrations —
e3f4a5b6c7d9(wiki entry soft delete) and
f4a5b6c7d8ea(campaign soft delete). Both add nullable columns and partial
indexes only; no data is rewritten, and both were verified to apply, roll back
and re-apply cleanly. For a major upgrade take a snapshot first —make migrate-safedoes the preflight, backup and migrate in one step.The bot contract is unchanged (still v1), so you can upgrade the backend and
bot images independently if you need to. Everything added to/api/bot/*this
release is optional on both sides.⚠️ Deleted things now occupy disk for 30 days. Campaigns and wiki entries go
to a trash rather than being removed immediately, and are purged by a daily job
once the grace period elapses. The window is enforced with a 7-day floor in
code — configuring a shorter one is clamped up, deliberately, because these are
artefacts nobody can reconstruct.New optional setting:
RESTART_POLICYin.env. Leave it unset unless
something else on your host owns boot ordering; see.env.example.Added
-
[webapp] The container restart policy can now be set per host. A new
optionalRESTART_POLICYin.env. Left unset it behaves exactly as before
— the stack comes back on its own after a reboot — so no existing install
needs to change anything.Set it to
on-failureonly if something else on your host is responsible for
starting stacks in order, such as a boot sequencer pacing many Compose stacks
across shared disks. It still restarts containers that crash; it only stops
Docker bringing the stack up by itself when the daemon starts. -
[webapp] Deleting a campaign can now be undone, and is much harder to do by
accident. Deleting a campaign removed everything in it — every session,
transcript, summary, wiki entry, note and ledger entry — immediately and
permanently, and the only thing standing in the way was a browser dialog
that any GM on the campaign could dismiss on reflex. There was no undo, no
pause, and nothing suggesting you take a copy first.Deleting now asks you to type the campaign's name, offers to export the
campaign before you go ahead, and puts it in a trash for 30 days rather than
destroying it. Deleted campaigns appear at the bottom of your dashboard
with a countdown, and restoring one brings back everything exactly as it was.
Nothing is deleted for real until the 30 days are up.Archiving is unchanged and still means what it always did — a campaign moved
out of the active list that you can still read, browse and export. -
[webapp] Deleting a wiki entry can now be undone. Deleting an entry used
to remove it permanently and immediately — along with its entire edit
history, which went in the same instant. A GM who deleted what looked like a
duplicate, and realised afterwards it was the real entry with months of lore
in it, had nothing at all to go back to.Deleted entries now sit in a trash for 30 days, where a GM can put them back
with everything intact. Admin → your campaign's wiki trash lists what is
in there and when each one stops being recoverable. -
[webapp] A wiki entry can be rolled back to an earlier version. The edit
history has always been visible and never usable: recovering an older draft
meant copying it out of the history view and pasting it back in by hand, and
the sidebar and timeline fields could not realistically be restored at all.
There is now a restore button on each version.Restoring saves the current state first, so if you restore the wrong version
you can simply restore your way back.
Changed
-
[bot] Recordings now take a sixth of the disk space they used to. The bot
was storing audio in Discord's format and converting it after the session
ended. It now converts as it records, so a six-hour session occupies about
0.7 GB per speaker instead of 4.2 GB, and there is no conversion step to wait
through when you stop recording.Audio quality is unchanged — the conversion filters the audio properly on the
way down, the same as the old post-processing step did. Recordings made by an
older version are still readable, and are converted the way they always were.
Added
-
[webapp] Backups can now leave the machine they are protecting. Until now
the scheduled backup wrote a database dump to a folder on the same host and
disk as the database itself. Losing that host — a failed disk, a
deprovisioned server, ransomware — took the database, the recordings, and
every backup of the database at the same moment.Set an off-host destination in Admin → Backups and each backup is copied
there as well. It uses rclone, so the same setting works for S3, Backblaze,
a NAS over SFTP, and most other storage; your storage credentials live in
rclone's own configuration and are never stored by Quest Board.A backup that cannot be copied off-host is now reported as failed, even
though the dump itself worked. A backup sitting on the disk it was supposed
to protect is not a backup, and calling it a success is how an instance finds
out it has none at the worst possible moment. The local copy is still kept. -
[webapp] Recordings can be included in backups. A database dump cannot
contain audio files. Switch on Include audio recordings and each backup
gains a compressed archive of them.It is off by default because recordings are far larger than a dump — check
your disk space before enabling it. But if you keep recordings at all, you
probably want it: a session whose transcription failed has no transcript, so
its audio is the only record that the session happened.
Fixed
-
[webapp] Granting admin by email no longer crashes when you have two
accounts. Accounts are identified by which login provider you came from,
not by your email address, so the same person can hold more than one — most
commonly after copying a production database onto a test machine, where your
test login is a separate account that was never made an admin.make set-admin EMAIL=…used to fail with a raw database error in exactly
that situation. It now lists the matching accounts, shows which one already
has admin, and tells you how to pick the right one. Nothing is changed until
you do. The operations guide has a section on this, since it looks like lost
access when it is not. -
[docs] The operations guide told you to leave recordings out of your
backups. It described the audio volume as scratch space that "does not need
to survive a restore". That stopped being true some time ago — recordings
stay until a GM approves them, a campaign can be set to keep them forever,
and a failed transcription leaves the audio as the only copy of anything.
Anyone who followed that advice would have lost every recording on the
instance during a restore. The guide now describes what the volume actually
holds, and how to back it up and restore it.
Changed
-
[webapp] Build tooling:
@vitejs/plugin-react6.0.5 → 6.1.1. Purely a
build-time dependency; nothing about the app itself changes.The update had been stuck since it was first offered, because 6.1.1 declares
an optional peer whose own optional peer wants Babel 8 while the project is
on Babel 7 — enough for npm to refuse the update even though none of those
packages are actually installed. A singleoverridesentry stops npm chasing
that chain, which unblocks it without dragging in 61 unrelated dependency
bumps as the alternative would have.
Fixed
-
[bot] A recording interrupted by a crash now finishes itself on the next
start. If the bot went down mid-session, the audio it had already captured
was left as raw working files, and there was no way to get it to Quest Board
short of someone converting it by hand — the earlier fix stopped that audio
being deleted, but stopped there. A recording that was interrupted an hour
into a four-hour session sat on disk indefinitely.On its next start the bot now converts what it captured and hands it over for
transcription, and the session appears as normal. Nothing is reconstructed:
the tracks were already recorded against a single session clock, so the
timings and who-said-what are the real ones. Speakers are named the way they
are on any other recording — server nickname first, so a player whose
nickname is their character's name still shows up as that character.It errs towards leaving the audio alone. If it cannot reach Quest Board, or
the session's group has no Discord server attached, it changes nothing and
tries again next start. If the session already has a transcript it keeps the
take but will not overwrite what is there.
Added
-
Recordings left behind by a deleted campaign are now reclaimed. Deleting
a campaign removed its sessions but left every recording on disk, and nothing
ever cleaned them up — one sweep only looks at sessions that still exist, and
the other treats a handed-off recording as permanently off-limits. On a busy
instance that is storage that only ever grows.A daily job now removes recording folders that no session refers to, after
giving each a week's grace, and records what it removed in the audit log.It is deliberately cautious about it: it only ever touches folders it
recognises as its own, it leaves anything recent alone, and if the session
list comes back empty it refuses to do anything at all — an instance with
recordings on disk and no sessions has a database problem, and treating that
as "everything is an orphan" would delete every recording on the volume.
Added
-
The bot event queue's size is configurable, and reports whether it is big
enough. Notifications for the bot wait in a queue capped at 10,000 — enough
for one group, but every group on an instance shares it. A bot down long
enough for its unread notifications to age out loses them; the queue is what
makes delivery reliable, so there is no catching up afterwards.BOT_EVENTS_MAXLENnow sets that cap, and a new
bot_events_trimmed_unread_totalmetric reports whether notifications are
actually being lost — so the number can be sized from evidence instead of
guessed at. It reads nothing rather than zero when there is not yet enough
data to tell, since "nothing to measure" should not look like "nothing
wrong".
Fixed
-
Unsaved writing is much harder to lose. Three surfaces could discard
work with no warning:- The Session Shelf's tonight-notes — the field you type into during
play — only saved when it lost focus. Closing the tab with the cursor
still in it lost everything since the last time you clicked away. It now
saves as you type, a moment after you stop. - The Beat Planner discarded your draft when you switched sessions in
its own dropdown. It now asks first. - Session summary and transcript editors, the Beat Planner and the Shelf
all now warn before a tab close or reload with unsaved text.
Note that the browser shows its own generic "Leave site?" wording for that
warning — it does not let an app supply the message. Warning on in-app
navigation needs a change to how the app routes pages and is tracked
separately. - The Session Shelf's tonight-notes — the field you type into during
Fixed
-
[bot] A vote that doesn't reach Quest Board now tells you. Reacting to a
poll in Discord leaves your emoji on the message whether or not the vote was
actually recorded — so a brief backend outage during a voting window meant
several people believed they had voted when they hadn't. Voting would then
close on the tally that happened to arrive, confirm a time, and nobody would
ever learn a vote had gone missing.A reaction that fails to register now gets you a DM saying so and what to do,
while there is still time to fix it. If your DMs are closed, the bot marks the
message with ❗ instead. Successful reactions stay silent.The same applies to RSVPs and summary feedback, and to reactions on a poll
Quest Board has stopped tracking (an old poll, or a bot restart) — previously
indistinguishable from reacting to any other message, and equally silent.
Fixed
-
A departing member's shared notes are kept when someone else has already
left. Deleting an account anonymises what you wrote onto a shared "Deleted
user" record. If a second person later deleted their account and had written
on the same session, their note collided with the first one and was thrown
away instead — so the campaign quietly lost one person's writing, in a way
indistinguishable from it never having been written.Both accounts' notes are now kept together on that session.
Votes and attendance still cannot be merged — one vote per person is the
point of the rule, and there is only ever one "Deleted user" — so a second
departure genuinely does reduce those tallies by one. That was already true;
what is new is that it is now counted and recorded in the audit log rather
than happening silently.
Fixed
-
The campaign journal works for campaigns with more than one GM. It
errored outright for every member of a co-GM campaign — the page looked as
though everyone's notes had vanished, when in fact it was crashing while
loading them. Co-GMs are supported everywhere else in the app; this one query
assumed there was exactly one.Both GMs' public notes now appear, each labelled with who wrote it, so two
people's notes under one "GM Notes" heading no longer read as one person
contradicting themselves.
Fixed
- A queue hiccup no longer leaves lore generation stuck at "queued…". The
session was marked queued before the job was actually handed to the queue, so
a momentary blip left it claiming work that nobody would ever pick up — and
because it looked busy, re-running it was refused. The failure is now
reported straight away, as a failure, with the re-run available immediately
rather than after a wait.
Added
-
You are told before a recording is deleted for good. Audio reaches
deletion through the trash, and until now nothing announced that the clock
was running — the countdown existed only in the admin console, so noticing
required knowing to go and look there.Quest Board now posts to your campaign's Discord channel a few days out,
saying which recording, exactly when, and how to keep it. Once per trip
through the trash, not daily: a message that arrives every morning is one you
stop reading. The same countdown appears on the session page itself.It says explicitly when a session's transcription failed, because that is
the case where the recording is the only copy of anything — no transcript was
ever produced — and losing it loses the session rather than just its media.
Fixed
-
"Append to beat notes" no longer wipes them. The Workbench's session-prep
tool composed its append against a field the campaign session list has never
returned, so the existing notes always read as empty — the generated sheet
replaced whatever the GM had written, and the tool reported "Appended to
beat notes." A GM with an evening of hand-written prep lost it, and was told
the opposite. There is no undo for beat notes.The same root cause left the beat-notes editor opening blank no matter
what was stored, so typing there and saving replaced the stored notes too.Beat notes now have a read path and both places use it. If the current notes
cannot be loaded, appending fails and says so rather than composing against
nothing — a button labelled Append must never be able to replace. The notes
stay GM-only: the read is gated the same way the transcript is, so a player
cannot see the GM's plan for the evening. -
Nothing the app generates will overwrite what you wrote. Two paths did,
both silently, and neither keeps history — so the work was not merely
replaced, it was gone.The campaign storyline was rebuilt from raw session summaries every time
any session in the campaign was transcribed, re-transcribed, or had its
summary touched. An hour spent turning AI summaries into a readable chronicle
survived until the next recording. A hand-edited storyline is now left alone;
the "Regenerate" button still rebuilds it, because that is you asking.Picking a canonical name — settling what to call a tavern — re-summarised
the whole session and replaced the summary with it. A GM who had corrected
the AI's mistakes by hand got those mistakes back, from an action that only
claimed to name something. Naming no longer touches the summary at all; the
name still applies to everything generated afterwards, which is what it was
for.The two compounded: one canonical-name pick could destroy a corrected summary
and the campaign chronicle, because the first rewrite triggered the second.
Fixed
-
Two people editing the same thing no longer silently erase each other.
Notes, summaries and planning notes were saved last-write-wins with no check
of any kind, so whoever clicked Save second won and the other edit was gone —
no error, no warning, nothing in a log.The case that actually bit: you open a session on a laptop, type notes from
your phone over Discord all evening (which correctly appends them), then click
Save on the laptop out of habit at the end of the night. The laptop still held
the note as it looked before the session, and saving replaced the whole
evening with it.Saves now carry the version they were based on. If the thing changed
underneath you, the save is refused and you are told to reload rather than
finding out later that something is missing. Nothing is auto-merged and
nothing you typed is thrown away — you choose what to keep.Appending instead of replacing was the other option and is worse for a text
editor: fixing a typo would duplicate the whole note rather than correct it.
Refusing the write loses nothing either way.
Added
-
Four actions that could only be taken once can now be taken back.
Marking a session complete, cancelling one, approving a summary and having a
wiki draft stall were all one-way doors: no endpoint could reverse them, so a
mis-click or a lost worker left editing the database by hand as the only
repair.- Undo complete and Reinstate return a session to where it was. A
cancelled session goes back to confirmed if it still holds a time and to
proposed if it does not. Both are recorded in the audit log, because they
rewrite something players and Discord have already seen. - Withdraw approval lets a GM correct a summary and publish the
correction. Approving is what posts the summary to Discord, and it could
only happen once — so an error spotted after approval was permanent in the
channel no matter how many times the text was fixed in the app. Quotes are
unpublished from players while the approval is withdrawn, mirroring what
approving did. - A wiki draft stuck generating can be released without discarding it.
Discarding was the only exit and it deleted the draft outright, taking
every inline edit and every round of iteration with it. Releasing marks the
draft failed and changes nothing else, which is enough — iterating,
approving and discarding are all reachable from there. Guarded so a draft
that may still be running is left alone.
What none of these do is pretend the side effects never happened. A Discord
message already delivered is not recalled, shelf notes already merged into a
wiki entry's GM notes stay merged, and withdrawing an approval does not pull
the audio back out of the trash — that grace window is deliberately bounded
(see the retention change in v4.0.1) and an approve/withdraw cycle must not
be able to extend it. - Undo complete and Reinstate return a session to where it was. A
Fixed
-
[webapp] A stalled lore run no longer hides its own retry button. The
re-run action was hidden for every in-progress state, so a run whose worker
died left the session showing "Extracting lore candidates…" with nothing to
click. Once a run has gone half an hour without any sign of progress, the
action comes back, labelled so it is clear the run is being replaced rather
than queued alongside. A run that is merely slow keeps reporting progress at
each stage and is left alone.Closing the same hole from the other side: the endpoint behind that button
refused a second run only while the first was queued, not while it was
extracting or matching. Triggering it during either of those stages started a
second extraction over the same transcript — duplicate proposals, and
double the tokens. Nothing hit it in practice only because the button was
hidden, which is exactly the rule being relaxed here, so both halves ship
together. -
[webapp] A restart during processing no longer strands a session forever.
Celery acknowledged a job the moment it was received, so a worker that died
mid-run took the job with it — nothing was redelivered and nothing was logged.
The session had already been flipped to "Processing…" on intake, and both
retry buttons refuse to act on a session in that state, so the only way out
was an UPDATE against the database by hand. An ordinary
docker compose up -d --buildduring a three-hour transcription was enough to
cause it.Work is now acknowledged on completion, so the queue redelivers whatever a
dying worker was holding. Two safeguards ship with it, because redelivery
alone would turn a job that kills its worker into a job that kills every
worker in turn: a six-hour limit (above the six-hour recording maximum, so a
legitimate full-length transcription is never cut off) converts that into an
ordinary failure, and each worker now reserves one long job at a time rather
than a batch.An hourly watchdog is the backstop for anything redelivery cannot reach.
A job with no progress for eight hours is marked failed with a plain
explanation, which puts the retry button back within reach; audio, lore
extraction, entry drafts and Workbench generations are all covered. Nothing is
deleted, the audio path is left intact, and a job still legitimately running is
left alone. Transient network failures during transcription now retry twice on
their own before any of this applies. -
[bot] The bot no longer deletes recordings it told you were saved. Its
startup cleanup removed any session directory that was unmarked and more than
an hour old, and the "this is safe" marker was only written after a
successful upload. So a failed upload, a crash around the moment of upload, a
crash mid-recording, or the backend declining to overwrite an existing
transcript all left a real recording looking like abandoned scratch — and the
next restart destroyed it. In two of those cases the bot had just told the GM
in the channel that the recording was safe.Deletion now requires proof there is nothing to lose: anything holding a
non-empty track is kept, marked or not, and reported in the log instead. A
recording whose upload failed is also retried automatically on the next
start, replaying the original request rather than a reconstruction, so it
usually repairs itself without anyone noticing.Raw capture files from a crash mid-session are likewise kept rather than
deleted. They still need finalising by hand — the log names the path — but
they are no longer thrown away.
Changed
-
Scheduled database backups are now on by default, and land somewhere you
can see. They shipped disabled, which meant an instance only had backups if
its operator went looking for the setting — production ran for months with no
scheduled dump ever taken. They now run nightly out of the box, writing to
./backupsbeside yourdocker-compose.ymlinstead of a Docker named
volume buried under/var/lib/docker. That is the difference between a file
your own backup tooling already sweeps up and one it never sees. An install
that has deliberately turned backups off keeps that choice.Upgrading: your existing dumps are still in the
quest-board_backups
volume. Nothing moves or deletes them —docs/OPERATIONS.mdhas a one-liner
to copy them across when you are ready.Still not covered: the media and audio volumes, and any off-host copy. A
backup on the same disk survives a bad migration, not a dead machine. Point
your own tooling at./backupsin the meantime.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
v4.0.1
StableAll checks were successfulCI / Frontend tests, audit, and build (push) Successful in 1m18sCI / Backend lint (ruff) (push) Successful in 33sCI / Docker image build (push) Successful in 13sRelease / Create Forgejo release (push) Successful in 19sCI / Bot/backend version sync (push) Successful in 59sCI / Summarisation accuracy eval harness (stub provider) (push) Successful in 2m41sRelease / Build and push versioned images (push) Successful in 1m26sCI / Backend migration, tests, and audit (push) Successful in 4m54sCI / Bot tests and audit (push) Successful in 4m38sreleased this
2026-08-29 07:01:42 +00:00 | 2724 commits to main since this releaseRaw audio is never destroyed without a recovery window, and the secrets at
rest are both readable and actually encrypted.No migrations. Nothing to rehearse, nothing to back out.
⚠️ Rebuild, do not pull. The backend image changes (it now pins its
PostgreSQL client to the database's major version), so deploy with
docker compose up -d --build. A pull-and-restart will not apply it.⚠️ Audio now occupies disk for longer. Every retention mode reaches
deletion through the trash, so audio survives its retention window plus the
trash grace (minimum 7 days) rather than being removed the moment the window
expires. Under the previous default it was deleted during processing and
occupied nothing at all.If the startup log names a setting as having been stored in plaintext,
rotate that credential. Re-encrypting protects it going forward; it does not
un-expose a value that was already readable in every backup taken until then.Security
- Sensitive settings stored in plaintext are now encrypted on startup.
Encryption only ever happened on write, and rows written before it was
introduced were passed through on read forever — so a credential that had
never been rotated since could still be sitting in clear text in the database
and in every backup, with nothing reporting it. Any such row is re-encrypted
at startup and logged by name. If a row is reported, rotate that credential
— it was readable in every backup taken until now.
Fixed
-
Database backups are restorable again. The backup ran
pg_dumpfrom the
backend image while a restore runspg_restoreagainst the database image,
and the two were on different major versions — so every dump was rejected by
the server's own tooling withunsupported version (1.16) in file header,
while the job reported success and recorded it as one. The backend image now
pins its client to the database's major version, a test asserts the two stay
in step, and the backup refuses to run rather than writing a dump it knows
cannot be restored. Existing dumps written before this are not restorable
without a matching newer client — replace them by taking a fresh backup
after upgrading.
crashes on first use.** Sensitive settings are encrypted with a key derived
fromSECRET_KEY, which lives in.envand is not part of a database dump —
so restoring elsewhere left them unreadable, and reading one raised a raw
cryptography error out of the settings layer instead of taking the
"not configured" path every caller already handles. Unreadable settings now
degrade to unconfigured, are reported once and clearly, and are listed at
startup so the problem is found during the restore rather than days later
when a session fails to summarise.docs/OPERATIONS.mdnow states that
SECRET_KEYmust be backed up alongside the database. -
Admin can distinguish "never configured" from "configured but
unreadable". They render identically in every settings form, which is how a
restore produced a system that looked complete and was not. -
Raw audio is no longer deleted the moment a transcript exists. The
recommended retention mode destroyed the per-speaker WAVs inline during
processing, on the reasoning that there was nothing left to reprocess. That is
wrong in the case that matters, because the pipeline itself changes: v4.0.0
rewrote transcription, extraction, validation and composition, and no real
recording survived to validate any of it against. The mode is now
trash_after_processing— the audio moves to the trash, stays recoverable
there, and is deleted only once the grace has elapsed. The old
delete_after_processingname is still accepted from existing configuration
and normalised on read, so no action is needed on upgrade. -
The trash grace is a floor, not a default. A configured value below seven
days is clamped up, so a misconfiguration cannot reintroduce same-day loss. -
An expiring retention window no longer deletes directly. Under
retain_daysthe sweeper now moves expired audio to the trash rather than
removing it, so every mode reaches deletion through the same recoverable
window. Audio therefore lives for the configured window plus the trash
grace — budget disk accordingly. -
A trashed recording is no longer deleted early by the retention window.
The grace-period sweep and the policy sweep raced, and the shorter one won: a
recording trashed today, whose window had already passed, was removed on the
next scheduled run without its grace ever being observed. -
A silent speaker is no longer reported as having spoken. The fix that was
supposed to report a backend-dropped silent track as present-but-uncaptured
was overridden, because the same person was still counted among the captured
speakers and that assignment won. It only affected recordings the current bot
did not produce — which is precisely the case the guard existed for.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Sensitive settings stored in plaintext are now encrypted on startup.
-
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 | 2729 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
-
v3.11.4
StableAll checks were successfulCI / Backend lint (ruff) (push) Successful in 26sCI / Bot/backend version sync (push) Successful in 23sCI / Docker image build (push) Successful in 11sCI / Bot tests and audit (push) Successful in 2m27sCI / Frontend tests, audit, and build (push) Successful in 2m36sCI / Backend migration, tests, and audit (push) Successful in 6m9sRelease / Create Forgejo release (push) Successful in 12sRelease / Build and push versioned images (push) Successful in 21sreleased this
2026-08-07 06:18:26 +00:00 | 2828 commits to main since this releaseSilent failures made visible, and a frontend toolchain that had been building
on an end-of-life runtime.No migrations;
BOT_CONTRACT_VERSIONstays 1, so the backend and bot images can
be upgraded independently. Self-hosters who build their own images will pick up
Node 22 for the frontend stages automatically — nothing to change by hand.Changed
-
[webapp] Migrated the router from
react-router-dom7 toreact-router8.
In v8 thereact-router-dompackage was folded intoreact-routeritself and
is no longer published, so staying on 7.x meant staying on a line that will
receive no further fixes. This also clears the last allowlisted advisory
(GHSA-qwww-vcr4-c8h2): the allowlist is now empty, so the audit gate is a
real check rather than a formality. No behaviour change — every routing API in
use is exported unchanged from the new package. (#284) -
[webapp] The frontend is now built and tested on Node 22 instead of Node 20.
Node 20 reached end-of-life on 2026-04-30 and no longer receives security
patches, so the production frontend image was being built on an unsupported
runtime. CI and both Dockerfile frontend stages move together, deliberately —
testing on one major while building the image with another is how a
runtime-only break ships green. This also unblocks #284, since react-router v8
requires Node ≥ 22.22.0. (#284)
Fixed
-
[webapp] Approving a wiki proposal with no working LLM quietly produced a
worse entry. When an LLM was unavailable, approving a proposal that updates
an existing entry pasted both versions together instead of merging them, and
said nothing — the result was indistinguishable from a successful merge. It
still falls back to appending, since that beats refusing the approval, but it
now records that it did. This also covers a misconfigured endpoint rather
than merely an unconfigured one: a malformed URL was silently treated as "no
LLM configured", sending GMs to a settings page that already had a value in
it. That case is now logged too. (#287) -
[ops] Docker builds no longer ship the whole working tree as build context.
The repository had no.dockerignore, so every build of every target sent
~226 MB ofnode_modulesplus the full.githistory to the daemon. On a
Windows host it also failed outright, because npm's symlinks under
node_modules/.bindo not survive the bind mount — making it impossible to
build the production image locally to verify a change. CI never saw it, since
a fresh checkout has nonode_modules. -
[webapp] A response the model cut short was silently turned into a partial
result. When an LLM stopped because it hit its output budget, the transport
returned the half-finished body as though it were complete. The tolerant JSON
parser then salvaged the longest valid prefix from it, so a reply that should
have carried twelve wiki proposals could arrive as three — indistinguishable
from a session that genuinely produced three, and logged as a successful
recovery. Truncated structured responses are now rejected so the caller fails
loudly and can retry, rather than persisting a quietly incomplete result.
Truncated prose is still returned, since it is shortened rather than
corrupted. Covers all four providers — llama.cpp, OpenAI, Anthropic and
Ollama, each of which signals this condition differently. (#293)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
v3.11.3
StableAll checks were successfulCI / Docker image build (push) Successful in 18sCI / Frontend tests, audit, and build (push) Successful in 1m17sRelease / Create Forgejo release (push) Successful in 34sCI / Bot/backend version sync (push) Successful in 2m23sRelease / Build and push versioned images (push) Successful in 54sCI / Backend lint (ruff) (push) Successful in 2m32sCI / Bot tests and audit (push) Successful in 4m41sCI / Backend migration, tests, and audit (push) Successful in 8m24sreleased this
2026-08-06 17:04:39 +00:00 | 2839 commits to main since this releaseWiki entry summaries came out as JSON.
v3.11.2 was tagged but never deployed to production — this bug was caught on
the dev deploy and supersedes it. Upgrade straight to 3.11.3. No migrations;
BOT_CONTRACT_VERSIONstays 1.Fixed
-
[webapp] Wiki entry summaries were generated as JSON instead of prose. The
summary generator asked the model for "plain prose only" while the underlying
transport was still in JSON mode with the lore-proposal instructions attached,
so entries came out with a summary of{"proposals":[]}. The bug predates
v3.11.2, but was only reachable through the manual "regenerate summary" button;
v3.11.2 made summaries generate automatically on every approval, which would
have written JSON into the summary of every entry a GM approved. (#285)Existing installs are unlikely to be affected — the generator had no automatic
trigger before v3.11.2, so only entries where the manual button was used could
carry a JSON summary. Re-running "regenerate summary" on such an entry now
produces prose.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
v3.11.2
StableAll checks were successfulCI / Backend lint (ruff) (push) Successful in 30sCI / Bot/backend version sync (push) Successful in 23sCI / Docker image build (push) Successful in 10sCI / Bot tests and audit (push) Successful in 1m20sRelease / Create Forgejo release (push) Successful in 12sRelease / Build and push versioned images (push) Successful in 52sCI / Frontend tests, audit, and build (push) Successful in 2m48sCI / Backend migration, tests, and audit (push) Successful in 7m52sreleased this
2026-08-06 01:00:07 +00:00 | 2842 commits to main since this releaseThe rest of the LLM trigger audit, and a test suite that stops waiting on a
broker that was never there.Follow-ups to the audit that produced v3.11.1: five features that were wired to
the wrong event, or to no event at all. No migrations, and
BOT_CONTRACT_VERSIONstays 1 — nothing touches/api/bot/*, so images can be
upgraded independently.Fixed
- [webapp] The session prep sheet only saw the opening of the last session.
Every recent summary was truncated to a flat 800 characters, against real
summaries of 1700-2900 — so prep was drafted from roughly the first third and
never saw where the party ended up. Truncation also kept the beginning,
discarding the ending, which for prep is the most useful part of a summary.
The most recent session now gets a near-full budget and, if it ever exceeds
it, keeps both its opening and its closing. (#271) - [webapp] Lore generation could hang forever. If a phase of the multi-pass
lore pipeline ran out of retries, nothing cleared the session'sextractingor
matchingstatus — the UI showed "generating…" indefinitely, with no error.
A brief network blip to the LLM host was enough to trigger it. A missing LLM
endpoint now also fails immediately instead of burning several minutes of
backoff on a condition that cannot resolve itself. (#287) - [webapp] Wiki entries never got their one-line summary. The generator had a
single caller — the manual "regenerate summary" button — so every entry created
by approving a bot proposal, promoting a shelf card, or picking a canonical
name had an empty summary permanently. Summaries are now generated whenever an
entry becomes canon. (#285) - [webapp] Relationship suggestions never saw the session that just happened.
They were generated the moment the lore pipeline finished, but only consider
approved entries — and everything just produced was still an unapproved
proposal. They now run when you approve entries, debounced so working through a
batch produces one pass rather than one per click. (#286) - [webapp] Some GM Workbench generations were missing from history. Three
paths ran tools without recording them, so those generations never appeared in
Workbench history and their token usage went uncounted. All inline tool runs are
now recorded, including failed ones. Caches are unchanged. (#288) - [webapp] A queued background job could stall a request. With an
unreachable Redis, a single enqueue took about two minutes to fail, which
presented as a hung request rather than a degraded background job. Now bounded
to under 15 seconds.
Changed
- [dev] The backend test suite runs in 36 seconds instead of 16 minutes for
the same 880 tests. Most of that time was never work — with no broker in the
test environment, every queued job waited for a connection that could not
happen. Applies to CI too, which runs Postgres but no Redis.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [webapp] The session prep sheet only saw the opening of the last session.
-
v3.11.1
StableAll checks were successfulCI / Frontend tests, audit, and build (push) Successful in 1m54sCI / Bot/backend version sync (push) Successful in 24sCI / Docker image build (push) Successful in 13sCI / Backend lint (ruff) (push) Successful in 42sRelease / Create Forgejo release (push) Successful in 26sCI / Bot tests and audit (push) Successful in 1m52sRelease / Build and push versioned images (push) Successful in 55sCI / Backend migration, tests, and audit (push) Successful in 9m1sreleased this
2026-08-05 18:32:14 +00:00 | 2851 commits to main since this releaseApproval was unreachable, and the quote board was failing silently.
Three bug fixes found while investigating why a recurring session was offered
no title suggestions. One new migration (e0f1a2b3c4d5), additive and with
a best-effort backfill.BOT_CONTRACT_VERSIONstays 1 — no/api/bot/*change,
so images can be upgraded one at a time.Fixed
- [webapp] GM approval was unreachable on a default install.
sessions. audio_processing_statusconflated where the audio file is with whether the
GM had signed off on the content. Under the default retention mode
(delete_after_processing) the status jumps straight toapproved— which
means "audio deleted", not "GM approved" — so the approve endpoint, which
requiredready, could never be called. Everything gated behind it was dead:
publishing session highlights to players, reposting the GM-corrected summary
to Discord, journal entries, and next-session title suggestions. Content
approval now has its own state and its own endpoint,
POST /sessions/{id}/approve, gated on having a summary rather than on audio
state.POST /sessions/{id}/audio/approveremains as a deprecated shim.
Transcript editing and the feedback tally were gated the same way and are
likewise fixed. (#278) - [webapp] Post-summary tasks fired from three places that didn't know about
each other, so a hand-edited summary rebuilt the campaign storyline and
nothing else. Replaced with a single fan-out that distinguishes the initial
transcription from a later edit — the recap email and lore extraction no
longer re-fire because a GM fixed a typo. (#278) - [webapp] Title suggestions generated from nothing, and destroyed
themselves on failure. The task read context fromcompletedsessions but
runs while the session is stillconfirmed, and on any LLM error it
overwrote a good suggestion set with an empty one plus a fresh timestamp.
(#278) - [webapp] The quote board was silently empty. Endpoints that accept
response_format: json_objectwithout enforcing it can return structurally
invalid JSON, which the strict parser rejected — logging the result as
"0 highlights", indistinguishable from the model finding nothing memorable.
JSON parsing is now tolerant of a truncated or over-closed response, shared
across every structured-output caller, and both repair and total failure are
logged at WARNING. A related bug cleared existing highlights before checking
whether the new extraction returned anything, so each retry of a failing
session destroyed the previous good set. (#279) - [webapp] Admin retention warning for "delete after processing" understated
the consequences; it now tells you to correct the transcript before approving.
(#278)
Added
- [webapp] Title suggestions on the session edit form — sessions created
from a recurring series are materialized by a background task and never pass
through the new-session form, so this is the only place they can be titled
from a suggestion. (#278)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [webapp] GM approval was unreachable on a default install.
-
v3.11.0
StableAll checks were successfulCI / Docker image build (push) Successful in 15sCI / Backend lint (ruff) (push) Successful in 47sRelease / Create Forgejo release (push) Successful in 18sCI / Frontend tests, audit, and build (push) Successful in 1m16sRelease / Build and push versioned images (push) Successful in 1m11sCI / Bot tests and audit (push) Successful in 2m9sCI / Backend migration, tests, and audit (push) Successful in 3m26sreleased this
2026-07-27 19:55:48 +00:00 | 2870 commits to main since this releaseThe Session Reference Shelf: everything relevant to tonight, in one place.
A per-session board the GM keeps open during play. Anything generated in the GM
Workbench persists on the shelf for the whole session — whether or not it ever
becomes canon — and existing wiki entries can be pinned alongside it, so there is
no hunting a large wiki mid-scene. Cards hold the position you drop them in and
are never auto-reflowed; the shelf is a spatial surface, not a list.The whole feature works offline: no LLM and no transcription is required for any
of it (the transcript-driven layer is v3.12). One new migration
(d9e0f1a2b3c4), andBOT_CONTRACT_VERSIONstays 1 — the one new bot event is
additive, so a mixed-version bot keeps working.Added
- [webapp] The shelf itself — a per-session board of reference cards backed
by a newShelfCardmodel and GM-only API, with freeform drag-to-place
positions that never auto-reflow, resizable cards, per-card "tonight" notes,
and removal. (#239, #240) - [webapp] Add to shelf from anywhere — a shared control on every GM
Workbench generation result and on wiki entries, defaulting to the campaign's
in-progress or next session. (#241) - [webapp] Glance-compressed cards — each card shows a scannable face
(name, a one-line want or hook, ~3 facts, a voice cue) with full detail one tap
away, so cards read at arm's length instead of as walls of text. (#242) - [webapp] Spotlight / "now playing" — promote a card to a large stage;
number keys 1-9 or the card's stage button pick it, arrows or[]swap, Esc
clears. The board card never moves. (#243) - [webapp] Scene packs — bundle cards into named scenes during prep and swap
the board's scene in one tap during play. Swapping hides cards, it never moves
them, so every scene keeps the exact layout you left it in. (#244) - [webapp] Reveal to table — show a card to the players on Discord, in Quest
Board, or both. Each card carries a player-safe layer separate from its
GM-only content; when you haven't written one, the server derives a
conservative default from read-aloud text and public summaries only — never GM
notes or secrets — and refuses to reveal rather than guess when nothing is
confidently safe. Players get a new "At the table" view showing only what has
been revealed. (#245) - [webapp] Fast retrieval — a Ctrl-K fuzzy quick-switcher over tonight's
shelf first and then the wiki, plus number-key hotkeys for the cards on
screen. (#246) - [webapp] End-of-session sweep — one pass over every card touched tonight:
promote it to the wiki (an AI draft cleans and merges it through the existing
review rail), carry it forward, or discard it. (#247) - [webapp] Carry-forward pins + session navigation — pin a card and it rides
forward onto every later session automatically, as an interval over the
campaign's session timeline. Wiki cards render live; generated cards stay
frozen to their snapshot. Completing a session harvests each wiki card's notes
into that entry's GM notes; cancelling prunes untouched carried cards. Plus a
session switcher, a Workbench entry point, and "copy from another session".
(#252, #254) - [webapp] Warm cold-open — the shelf opens with a "previously on" recap, the
plot threads still open, and a count of the cards carried in, so session N+1
starts warm. The recap steps over cancelled and unsummarised sessions to find
the last one that actually has something to say. (#248) - [webapp] AI story-arc suggester — a GM Workbench tool that reads recent
sessions, arcs, threads, and wiki entries to propose candidate story arcs in
the result rail; accepting one creates aCampaignArc. (#236) - [bot] Revealed cards post to Discord — a new additive
shelf_revealevent
posts a card's player-safe layer to the campaign channel. The GM-only side of
a card never leaves Quest Board. (#245)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [webapp] The shelf itself — a per-session board of reference cards backed
-
v3.10.0
StableSome checks failedRelease / Create Forgejo release (push) Failing after 19sCI / Docker image build (push) Successful in 54sCI / Backend lint (ruff) (push) Successful in 1m39sRelease / Build and push versioned images (push) Successful in 1m16sCI / Frontend tests, audit, and build (push) Successful in 2m35sCI / Bot tests and audit (push) Successful in 4m55sCI / Backend migration, tests, and audit (push) Successful in 7m42sreleased this
2026-07-22 02:17:42 +00:00 | 2919 commits to main since this releaseGM Workbench: campaign-grounded generation, right where you plan.
GM Planning becomes a GM Workbench — a unified surface of AI tools that read your campaign's lore, plot threads, and session history to generate material you can drop straight into play or land into the wiki, all through one generation engine with a persistent scratchpad and the existing draft-review rail (nothing auto-overwrites your canon).
BOT_CONTRACT_VERSIONstays 1; no new migrations beyond the Phase-0 scratchpad table.Added
- Generation engine + scratchpad foundation — a tool registry and one generalized
POST …/workbench/{tool}/generateendpoint (sync inline or async via Celery), an extracted LLM transport, and aGenerationResulthistory/scratchpad. (#134, #136, #138) - Read-aloud descriptions, rumor mill, random tables, loot parcels, the flagship session prep sheet, instant NPCs + voice cues, and recurring-series title suggestions — each grounded in your campaign and landing through the draft-review rail. (#141, #144, #147, #149, #158, #155, #191)
- Two-pane Workbench layout — a grouped tool palette, a center generation surface, and a persistent history rail. (#153)
Changed
- "GM Planning" is now the "GM Workbench" — renamed throughout (nav and header), with the eight generator tools collapsed onto one config-driven panel. (#234)
- Searchable lore grounding — a searchable, type-grouped picker replaces the flat checkbox list, scaling to large campaigns. (#234)
Fixed
- Reasoning LLMs no longer break structured generation — json-mode llama.cpp / vLLM calls disable the model's think block (
chat_template_kwargs.enable_thinking=false), so reasoning models (e.g. Qwen3) work with the Workbench. - "Nothing to convert" is no longer an error — stat conversion on an entry with no mappable stats shows a gentle informational note (nudging you to generate a stat block from scratch) instead of a red failure banner. (#233)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Generation engine + scratchpad foundation — a tool registry and one generalized