feat: GM Workbench improv NPC tools + generalized prefetch (#155) #222

Merged
claude-bot merged 1 commit from feat/155-improv-npc into feat/v3.10-gm-workbench 2026-07-19 17:02:25 +00:00
Contributor

Two at-the-table improv tools (C1 + C2), plus the prefetch generalization deferred from #136.

Generalized prefetch (the reusable infra)

  • ToolDef.pool_field — names the output list a prefetchable tool draws from.
  • workbench_pool_service — a per-campaign+tool Redis list: atomic LPOP draw, RPUSH fill (capped at 15, 2h TTL). The generalized counterpart of the name cache; Redis access lives here so tests patch the service seam, not the raw client.
  • prefetch_workbench_pool Celery task — lock + TTL, mirroring prefetch_name_options.
  • POST /workbench/{tool_id}/draw (pop one; queue a refill when the pool runs low; cold-pool inline fallback that buffers the rest) and POST /workbench/{tool_id}/prefetch (warm in the background). Both GM-only; 404 for non-prefetchable tools.

C1 — instant_npc (prefetchable, pool_field="npcs")

A fully-formed NPC — name, pronouns, occupation, want, secret, voice cue — drawn with zero visible LLM wait. Frontend InstantNpcGenerator warms the pool on mount, "Conjure an NPC" draws off it, and "Save as lore entry" creates an npc LoreEntry.

C2 — npc_voice

Performable voice/mannerism cues for an existing npc entry — scoped strictly to cues (backstory/statblocks stay owned by #130). Frontend NpcVoiceGenerator reuses the shared WorkbenchResultActions bar, so cues land as a new lore entry, as a review draft appended to the npc entry, or copied.

Notes

  • No new models or migrations.
  • Backend infra by me; the two frontend panels by a delegate, then reviewed + verified here.

Verification (Docker)

  • Backend 730 passed (incl. test_workbench_npc.py — warm-pool draw, low-pool refill trigger, cold-pool inline fallback, prefetch trigger, non-prefetchable/GM-only 404/403, npc_voice generate + persistence + 422, parser tolerance)
  • Frontend 389 passed / 41 files; prod build OK
  • ruff check + ruff format --check clean; eslint 0 errors

Closes #155.

Two at-the-table improv tools (C1 + C2), plus the **prefetch generalization** deferred from #136. ## Generalized prefetch (the reusable infra) - **`ToolDef.pool_field`** — names the output list a prefetchable tool draws from. - **`workbench_pool_service`** — a per-campaign+tool Redis list: atomic `LPOP` draw, `RPUSH` fill (capped at 15, 2h TTL). The generalized counterpart of the name cache; Redis access lives here so tests patch the service seam, not the raw client. - **`prefetch_workbench_pool`** Celery task — lock + TTL, mirroring `prefetch_name_options`. - **`POST /workbench/{tool_id}/draw`** (pop one; queue a refill when the pool runs low; cold-pool inline fallback that buffers the rest) and **`POST /workbench/{tool_id}/prefetch`** (warm in the background). Both GM-only; 404 for non-prefetchable tools. ## C1 — `instant_npc` (prefetchable, `pool_field="npcs"`) A fully-formed NPC — name, pronouns, occupation, want, secret, voice cue — drawn with **zero visible LLM wait**. Frontend `InstantNpcGenerator` warms the pool on mount, "Conjure an NPC" draws off it, and "Save as lore entry" creates an `npc` `LoreEntry`. ## C2 — `npc_voice` Performable voice/mannerism cues for an **existing** npc entry — scoped strictly to cues (backstory/statblocks stay owned by #130). Frontend `NpcVoiceGenerator` reuses the shared `WorkbenchResultActions` bar, so cues land as a new lore entry, as a **review draft appended to the npc entry**, or copied. ## Notes - No new models or migrations. - Backend infra by me; the two frontend panels by a delegate, then reviewed + verified here. ## Verification (Docker) - Backend **730 passed** (incl. `test_workbench_npc.py` — warm-pool draw, low-pool refill trigger, cold-pool inline fallback, prefetch trigger, non-prefetchable/GM-only 404/403, `npc_voice` generate + persistence + 422, parser tolerance) - Frontend **389 passed** / 41 files; prod build OK - `ruff check` + `ruff format --check` clean; eslint 0 errors Closes #155.
Two at-the-table improv tools on the #136 engine, plus the prefetch
generalization deferred from #136.

Generalized prefetch:
- ToolDef.pool_field names the output list a prefetchable tool draws from.
- workbench_pool_service: a per-campaign+tool Redis list (LPOP draw, RPUSH
  fill, capped, TTL) — the generalized counterpart of the name cache.
- prefetch_workbench_pool Celery task (lock + TTL, mirrors prefetch_name_options).
- POST /workbench/{tool_id}/draw (pop one, refill when low, cold-pool inline
  fallback) + POST /workbench/{tool_id}/prefetch (warm in background), GM-only.

C1 instant_npc (prefetchable, pool_field="npcs"): a fully-formed NPC (name,
pronouns, occupation, want, secret, voice cue) drawn with zero visible latency;
frontend InstantNpcGenerator warms the pool on mount, "Conjure an NPC" draws,
Save as npc lore entry.

C2 npc_voice: performable voice/mannerism cues for an existing npc entry
(scoped to cues only — backstory/statblocks stay with #130); frontend
NpcVoiceGenerator lands via the shared WorkbenchResultActions (save / append to
the npc entry as a review draft / copy).

No new models or migrations. Backend 730 passed; frontend 389 passed;
ruff + eslint clean; build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot merged commit 47a48eb61a into feat/v3.10-gm-workbench 2026-07-19 17:02:25 +00:00
claude-bot deleted branch feat/155-improv-npc 2026-07-19 17:02:26 +00:00
Sign in to join this conversation.
No description provided.