chore(release): v3.10.0 — GM Workbench #225
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/v3.10-gm-workbench"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Release PR for the v3.10.0 "GM Workbench" milestone — all 11 issues built on the
feat/v3.10-gm-workbenchintegration branch via per-issue PRs (#216–#224). Awaiting your review + dev testing before merge — I have not merged this.What ships
GM Planning becomes a GM Workbench: a unified surface of campaign-grounded AI tools on one generalized generation engine (#136) with an extracted LLM transport (#134) and a persistent per-campaign scratchpad/history (#138).
Release metadata
APP_VERSION3.9.1 → 3.10.0;BOT_CONTRACT_VERSIONstays 1 (no bot API changes).generation_resultstable (#138, headf0a1b2c3d4e5) already on this branch.Verification
Every issue was merged green: backend 735 passed, frontend 397 passed (42 files),
ruff check+ruff format --checkclean, eslint 0 errors, prod build OK. CI runs on this PR for the authoritative gate.⚠️ Not yet dev-tested against a live LLM
Per your note (away, can't test on dev yet): the generative tools are unit-tested with the LLM mocked, but none has been exercised against a live Ollama/API endpoint or the Redis prefetch pool on dev. Recommend deploying this branch to dev and smoke-testing the tools (esp. session prep's async polling and the instant-NPC pool) before merging to main.
Open follow-up for your call
#153 delivered the two-pane shell (palette + dispatcher + history rail); collapsing the 8 bespoke panels into a single config-driven
GeneratorPanelwith one uniform action row was deferred to avoid rewriting tested code you can't yet validate — say the word and I'll open a follow-up issue.Closes the v3.10.0 milestone (11/11 issues).
🤖 Generated with Claude Code
The single extension point for the GM Workbench: a GENERATOR_TOOLS registry (ToolDef) + POST /api/campaigns/{campaign_id}/workbench/{tool_id}/generate (GM-only) that dispatches sync (inline) or async (Celery, run_workbench_generation) per the tool's sync_allowed flag, writing every run into the #138 GenerationResult scratchpad. Adding a tool = adding one registry entry. - generation_service.py: ToolDef registry, ToolResult, run_tool generic executor (build declared context -> prompt -> llm_service.generate_structured_text -> parse) + context builders campaign_context (folds in #137 system context) / lore_context / threads_context / recent_sessions_context. Eager-loads game_system_ref and guards unloaded refs (sa_inspect().unloaded) against async lazy loads. - Names migrated as the first tool (tool_id="names"): a custom runner delegating to audio_service.generate_name_options so output — incl. #137 per-system hints — stays byte-identical and existing name tests pass unmodified. Custom-prompt names no longer require an active session (campaign-scoped). - Old endpoints kept as thin wrappers into the registry (sessions name-options, planning names cache-miss); planning prefetch left delegating to the same name generator with prefetchable=True as the declarative hook (full generalization folds into #155, the second prefetchable consumer). Foundation for the six generator tools. No migration (uses #138's GenerationResult); no bot API change. Verified in Docker: 684 backend tests (+10), ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>First generic-path GM Workbench tool. Adds the `description` generator (read-aloud one-liner + paragraph, tone-aware, optionally grounded in a lore entry) plus the reusable result-landing actions every later generator reuses: - Save as lore entry (reuses the lore-create endpoint) - Append to entry — POST /lore/{entry_id}/append-draft creates a READY review draft (no LLM call) reviewable/approvable in the wiki draft rail; GM-only, one draft per entry - Copy to clipboard Threads generation params into the context builders so entry-scoped tools can fold the picked lore entry into the prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Suggest titles for the next occurrence of a recurring session series, sourced from the previous occurrence's summary, with an always-available manual entry. - series_titles workbench tool: params {session_id}; a context builder folds the most recent *prior* summarised occurrence in the same series into the prompt; output {suggestions: [{title, description}]}. Tolerant parser. - SessionResponse now exposes series_id + series_occurrence_date (additive; not the bot contract) so the planning UI can identify series occurrences. - SeriesTitleSuggester panel: pick an upcoming series occurrence, suggest titles, "Use this title" writes title+description onto the occurrence via the existing session PATCH — or type your own. Degrades to manual with no error when there's no prior summary or the LLM is unconfigured. Per-occurrence titles already survive series edits: materialize_session_series inserts occurrences with on_conflict_do_nothing, so it never clobbers an edited title (criterion 1). No new models or migrations. Backend 735 passed; frontend 397 passed; ruff + eslint clean; build OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Deployed to Dev (
docker-test/ ai-dev-server) for smoke testing — 2026-07-21Branch
feat/v3.10-gm-workbench@8721dccbuilt and recreated viadocker compose up -d --build(prod-safe stack). Verified:f0a1b2c3d4e5— migrate one-shot appliede9f0a1b2c3d4 → f0a1b2c3d4e5(#138generation_results)/ready→ ok; all 7 services healthy⚠️ LLM connectivity finding — blocks live Workbench testing with the current Dev model
Dev LLM is configured to
http://10.3.0.28:8090, modelqwen3.5(OpenAI-compatible / llama.cpp path). Endpoint is reachable and the private IP passes the URL policy (ALLOW_PRIVATE_SERVICE_URLSis set on Dev). Butqwen3.5is a reasoning model that emits hiddenreasoning_content, and that breaks structured generation:_test_llm_llamacppcaps atmax_tokens=16; the model spends all 16 on reasoning and never reaches visiblecontent. Misleading but harmless.generate_structured_text, json_mode, 2048 cap): small outputs parse fine, but a large output (d20 table) fails —finish_reason='length',completion_tokens=2048, emptycontent→_structured_llamacppraises "empty response". Reasoning tokens exhaust the JSON budget before any answer is produced.This is a model-compat issue, not a v3.10 regression — it hits existing json_mode features (lore proposals, stat blocks) the same way. To smoke-test the Workbench, Dev needs a non-reasoning model (or the same model served with thinking disabled).
Follow-up worth considering (separate from this release): graceful reasoning-model support — send a disable-thinking hint where the server supports it, and/or detect
finish_reason='length'and surface a clear "model exhausted its token budget on reasoning" error instead of the generic empty-response message; also bump themax_tokens=16in the Test button so the Admin test is meaningful for reasoning models.🤖 Generated with Claude Code
Reasoning models served via llama.cpp/vLLM (e.g. Qwen3) spend the entire max_tokens budget on hidden reasoning_content before emitting any JSON, so every non-trivial Workbench/structured generation returned an empty or truncated response (finish_reason='length', empty content). Send chat_template_kwargs={"enable_thinking": false} on json_mode llama.cpp requests to turn the think block off; it is silently ignored by models that don't support it, and structured output never benefits from reasoning tokens. Adds transport-level tests asserting the json-mode vs prose request body. Surfaced while dev-testing the v3.10 GM Workbench against a local qwen3.5 llama.cpp server (#136). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Reasoning-model fix committed — Workbench now works with the Dev
qwen3.5serverRoot cause: the Dev LLM is
llama.cppserving the reasoning modelqwen3.5. Empirically, this build honors onlychat_template_kwargs.enable_thinking=falseto turn thinking off — top-levelenable_thinking, the/no_thinksoft-switch, andreasoning_effortwere all ignored (each still burned the full 2048-token budget onreasoning_contentand returned empty/truncated JSON).Fix (
07552a3; format-only follow-upebd5b7e):_structured_llamacppnow sendschat_template_kwargs={"enable_thinking": false}wheneverjson_modeis on. Scoped to structured output (which never benefits from reasoning tokens); silently ignored by models that don't support the switch. Backend-only, no contract bump. Addedtests/test_llm_transport.pyasserting the json-mode vs prose request body; CHANGELOG updated under[3.10.0] → Fixed.Live validation on Dev (real
generate_structured_texttransport → real endpoint): the previously-failing d20 table now returns full valid JSON — title + all 20 rows,finish=stop. Both small and large Workbench-style outputs succeed. Dev is running the functional fix (07552a3);ebd5b7eis test-formatting only and doesn't change runtime behavior.CI is re-running on
ebd5b7e(the first run was cancelled by this push; its only failure was the ruff-format check now fixed). Ready for live Workbench smoke-testing againstqwen3.5.🤖 Generated with Claude Code
Live Workbench smoke sweep on Dev — all tools + async core + prefetch pool ✅
Ran every registered tool through the real service layer (
generation_service.run_tool) against the liveqwen3.5llama.cpp endpoint, on campaign A Merry Band of Thieves (15 lore entries, real NPC + session). CI #4268 is green (backend incl. newtest_llm_transport.py, frontend, ruff).LootEntry-shaped fieldsready, all 5 sections populated, groundedNo failures. Outputs are genuinely campaign-grounded (reference Aramore, the Old Quarter, the party's dream figure). Both the async dispatch path (
run_workbench_generation_core) and the pool round-trip work end-to-end. The sweep left onesession_prepGenerationResult row in history and filled theinstant_npcpool (2h TTL) — both benign/self-cleaning.v3.10 is functionally validated end-to-end against a live reasoning model. Remaining before merge is your own UI pass + review.
🤖 Generated with Claude Code
A cleared number input reads as "" (Number("") === 0, not NaN), so the count field could land on an invalid 0; guard the empty case to snap back to the field default. Also key LorePicker's selectedIds memo on value+multiple so it stops recomputing every render. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>v3.10 scope expanded from dev testing — fully green, ready for review
Since this release PR opened, the branch grew beyond the original 11-issue milestone based on live dev testing. Full current scope of
feat/v3.10-gm-workbench:Added since the original PR:
07552a3) — json-mode llama.cpp calls now disable the model's think block (chat_template_kwargs.enable_thinking=false), so Qwen3 / DeepSeek-style reasoning models work with the Workbench. This is what made the devqwen3.5server usable end-to-end.GeneratorPanel(sync / async-poll / instant-draw) + a per-tool config module (workbenchTools.jsx). The flat lore-checkbox lists are replaced by a shared searchable, grouped, chip-basedLorePicker(server-side search — scales to large campaigns). Page renamed "GM Planning" → "GM Workbench" and the header overflow fixed. Old panels removed; tests ported toLorePicker/GeneratorPanel/workbenchToolsspecs.Verification:
22e3671(backend / bot / ruff / docker + frontend build + eslint + 387 vitest tests).qwen3.5earlier in this thread; the refactor is frontend-only, so the tool runners are unchanged.Closes #233 and #234 in addition to the original 11 issues.
Awaiting your UI click-through on dev + the merge call. (Reload past the PWA service worker to pick up the new bundle.)
🤖 Generated with Claude Code