[GM Workbench] Improv NPC panel with prefetch (C1 + C2) #155

Closed
opened 2026-07-15 22:04:09 +00:00 by claude-bot · 1 comment
Contributor

Motivation / Context

At-the-table improv is a distinct use case from prep-time generation: a GM needs a full, usable NPC (name, pronouns, occupation, want, secret, voice cue) instantly, with zero latency, when players go somewhere unplanned. The report ranks improv NPCs with instant recall into the wiki among the most differentiated Workbench tools (§6) — the moat isn't the generation itself but the "they matter now → wiki" one-click promotion, which no standalone generator site offers.

This combines two catalog entries (§3, Group C): C1 (instant NPC, served from a prefetched pool) and C2 (voice/mannerism cues for an existing npc entry).

Approach

C1 — Instant NPC panel:

  • Register an instant_npc tool in GENERATOR_TOOLS (generation_service.py, #136) flagged prefetchable=True.
  • Reuse the planning_tasks.py Redis prefetch pattern verbatim (§5.1's "prefetch reuse" guidance): a Celery task takes a per-campaign+tool lock (mirroring name_prefetch_lock:{campaign_id}, planning_tasks.py:53-60), generates a small pool of ready-to-use NPCs in the background, and caches them under a Redis key analogous to name_cache:{campaign_id}:{category} (planning_tasks.py:80) with a TTL. The generalized prefetch endpoint from #136 (which generalizes campaigns.py:3074-3090) triggers this.
  • Each pooled NPC: name, pronouns, occupation, want, secret, voice cue — via generate_structured_text in JSON mode, using campaign_context (+ game_system) from #136's context builders.
  • Frontend: a one-click "conjure an NPC" button pulls the next pooled entry with zero visible latency (same UX principle as the existing name cache read at campaigns.py:3093-3144), refilling the pool in the background as it's drawn down.
  • Output landing: "they matter now" → "Save as lore entry" creates an entry_type=npc LoreEntry immediately (same pattern as canonical names, sessions.py:283-296). Until saved, drawn NPCs are ephemeral/scratch (not necessarily persisted to GenerationResult — the prefetch pool itself is the buffer, consistent with how names work today).

C2 — Voice & mannerism prompts:

  • Register an npc_voice tool for generating 3 performable cues (speech pattern, verbal tic, physical mannerism, catchphrase) for an existing npc lore entry, reading its body + pronouns.
  • Output lands either as a Scratch result (GenerationResult, #138) or as a Draft appending a GM-only section to the entry — reuse start_merge_draft / append semantics (draft_service.py:93) and WikiDraftReview.jsx, the same rail #130 uses for its expand_backstory/generate_statblock draft modes. Do not duplicate #130's backstory/statblock generation — this tool is scoped strictly to performance cues (voice/mannerism), not backstory or stats.

Dependencies

  • #134 (LLM core), #136 (workbench endpoint + tool registry + prefetch generalization), #138 (scratchpad, for C2's scratch path) — Phase 0 foundation.
  • Builds on the prefetch pattern from planning_tasks.py, unchanged by this issue except for reuse.
  • No dependency on #130, but C2's draft-append path reuses the same draft-review infrastructure #130 extends; keep scope non-overlapping (voice/mannerism only, never backstory or statblocks — those stay owned by #130).

Out of scope

  • NPC backstory generation — for new entities that's A2 (a separate, not-yet-filed tool); for existing npc/creature entries it's owned entirely by #130's expand_backstory draft mode.
  • Statblocks — owned entirely by #130's generate_statblock draft mode.
  • Configurable prefetch pool size/categories beyond a sensible default — the report flags "make prefetch categories configurable" as a hosted-service cost consideration (§6) worth a follow-up, not required here.

Acceptance criteria

  • instant_npc tool is prefetchable; a background Celery task keeps a per-campaign pool populated using the same lock+TTL pattern as name prefetch.
  • Clicking "conjure an NPC" returns a fully-formed NPC (name, pronouns, occupation, want, secret, voice cue) with no visible LLM wait.
  • "Save as lore entry" creates an entry_type=npc LoreEntry from the drawn NPC.
  • npc_voice tool, given an existing npc entry, returns 3 performable cues (speech pattern, verbal tic, physical mannerism, catchphrase).
  • npc_voice output can be saved to GenerationResult (scratch) or proposed as a GM-only-section LoreEntryDraft append, reviewable in WikiDraftReview.jsx.
## Motivation / Context At-the-table improv is a distinct use case from prep-time generation: a GM needs a full, usable NPC (name, pronouns, occupation, want, secret, voice cue) *instantly*, with zero latency, when players go somewhere unplanned. The report ranks improv NPCs with instant recall into the wiki among the most differentiated Workbench tools (§6) — the moat isn't the generation itself but the "they matter now → wiki" one-click promotion, which no standalone generator site offers. This combines two catalog entries (§3, Group C): C1 (instant NPC, served from a prefetched pool) and C2 (voice/mannerism cues for an *existing* npc entry). ## Approach **C1 — Instant NPC panel:** - Register an `instant_npc` tool in `GENERATOR_TOOLS` (`generation_service.py`, #136) flagged `prefetchable=True`. - Reuse the `planning_tasks.py` Redis prefetch pattern verbatim (§5.1's "prefetch reuse" guidance): a Celery task takes a per-campaign+tool lock (mirroring `name_prefetch_lock:{campaign_id}`, `planning_tasks.py:53-60`), generates a small pool of ready-to-use NPCs in the background, and caches them under a Redis key analogous to `name_cache:{campaign_id}:{category}` (`planning_tasks.py:80`) with a TTL. The generalized prefetch endpoint from #136 (which generalizes `campaigns.py:3074-3090`) triggers this. - Each pooled NPC: name, pronouns, occupation, want, secret, voice cue — via `generate_structured_text` in JSON mode, using `campaign_context` (+ `game_system`) from #136's context builders. - Frontend: a one-click "conjure an NPC" button pulls the next pooled entry with zero visible latency (same UX principle as the existing name cache read at `campaigns.py:3093-3144`), refilling the pool in the background as it's drawn down. - **Output landing**: "they matter now" → **"Save as lore entry"** creates an `entry_type=npc` `LoreEntry` immediately (same pattern as canonical names, `sessions.py:283-296`). Until saved, drawn NPCs are ephemeral/scratch (not necessarily persisted to `GenerationResult` — the prefetch pool itself is the buffer, consistent with how names work today). **C2 — Voice & mannerism prompts:** - Register an `npc_voice` tool for generating 3 performable cues (speech pattern, verbal tic, physical mannerism, catchphrase) for an *existing* npc lore entry, reading its `body` + `pronouns`. - Output lands either as a Scratch result (`GenerationResult`, #138) or as a **Draft** appending a GM-only section to the entry — reuse `start_merge_draft` / append semantics (`draft_service.py:93`) and `WikiDraftReview.jsx`, the same rail #130 uses for its `expand_backstory`/`generate_statblock` draft modes. Do not duplicate #130's backstory/statblock generation — this tool is scoped strictly to performance cues (voice/mannerism), not backstory or stats. ## Dependencies - **#134** (LLM core), **#136** (workbench endpoint + tool registry + prefetch generalization), **#138** (scratchpad, for C2's scratch path) — Phase 0 foundation. - Builds on the prefetch pattern from `planning_tasks.py`, unchanged by this issue except for reuse. - No dependency on #130, but C2's draft-append path reuses the same draft-review infrastructure #130 extends; keep scope non-overlapping (voice/mannerism only, never backstory or statblocks — those stay owned by #130). ## Out of scope - NPC backstory generation — for new entities that's A2 (a separate, not-yet-filed tool); for existing npc/creature entries it's owned entirely by #130's `expand_backstory` draft mode. - Statblocks — owned entirely by #130's `generate_statblock` draft mode. - Configurable prefetch pool size/categories beyond a sensible default — the report flags "make prefetch categories configurable" as a hosted-service cost consideration (§6) worth a follow-up, not required here. ## Acceptance criteria - `instant_npc` tool is prefetchable; a background Celery task keeps a per-campaign pool populated using the same lock+TTL pattern as name prefetch. - Clicking "conjure an NPC" returns a fully-formed NPC (name, pronouns, occupation, want, secret, voice cue) with no visible LLM wait. - "Save as lore entry" creates an `entry_type=npc` `LoreEntry` from the drawn NPC. - `npc_voice` tool, given an existing npc entry, returns 3 performable cues (speech pattern, verbal tic, physical mannerism, catchphrase). - `npc_voice` output can be saved to `GenerationResult` (scratch) or proposed as a GM-only-section `LoreEntryDraft` append, reviewable in `WikiDraftReview.jsx`.
Author
Contributor

Done — merged to feat/v3.10-gm-workbench via PR #222.

Generalized prefetch (deferred here from #136): ToolDef.pool_field + workbench_pool_service (per-campaign+tool Redis list — atomic LPOP draw, RPUSH fill, capped, 2h TTL) + prefetch_workbench_pool Celery task (lock+TTL, mirrors prefetch_name_options) + POST /workbench/{tool_id}/draw and /prefetch endpoints (GM-only; 404 for non-prefetchable tools).

C1 instant_npc (prefetchable, pool_field="npcs"): draws a fully-formed NPC (name, pronouns, occupation, want, secret, voice cue) with zero visible LLM wait; cold-pool inline fallback buffers the rest. InstantNpcGenerator warms the pool on mount, conjures on click, saves as an npc LoreEntry.

C2 npc_voice: performable cues for an existing npc entry (cues only — backstory/statblocks stay with #130). NpcVoiceGenerator reuses WorkbenchResultActions, so cues land as a new entry, an appended review draft on the npc entry, or a copy.

All acceptance criteria met. No new models/migrations.

Verified: backend 730 passed (incl. new test_workbench_npc.py — warm/low/cold pool paths, prefetch trigger, 404/403 guards, npc_voice generate + 422 + parser tolerance), frontend 389 passed, ruff + eslint clean, build OK. Not yet exercised against a live LLM/Redis pool on dev — pending your dev testing.

Done — merged to `feat/v3.10-gm-workbench` via PR #222. **Generalized prefetch (deferred here from #136):** `ToolDef.pool_field` + `workbench_pool_service` (per-campaign+tool Redis list — atomic LPOP draw, RPUSH fill, capped, 2h TTL) + `prefetch_workbench_pool` Celery task (lock+TTL, mirrors `prefetch_name_options`) + `POST /workbench/{tool_id}/draw` and `/prefetch` endpoints (GM-only; 404 for non-prefetchable tools). **C1 `instant_npc`** (prefetchable, `pool_field="npcs"`): draws a fully-formed NPC (name, pronouns, occupation, want, secret, voice cue) with zero visible LLM wait; cold-pool inline fallback buffers the rest. `InstantNpcGenerator` warms the pool on mount, conjures on click, saves as an `npc` `LoreEntry`. **C2 `npc_voice`**: performable cues for an existing npc entry (cues only — backstory/statblocks stay with #130). `NpcVoiceGenerator` reuses `WorkbenchResultActions`, so cues land as a new entry, an **appended review draft on the npc entry**, or a copy. All acceptance criteria met. No new models/migrations. Verified: backend 730 passed (incl. new `test_workbench_npc.py` — warm/low/cold pool paths, prefetch trigger, 404/403 guards, npc_voice generate + 422 + parser tolerance), frontend 389 passed, ruff + eslint clean, build OK. **Not yet exercised against a live LLM/Redis pool on dev** — pending your dev testing.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#155
No description provided.