[Game Systems] System-aware LLM prompt context #137

Closed
opened 2026-07-15 22:01:06 +00:00 by claude-bot · 2 comments
Contributor

Motivation/Context

Every LLM feature that references the game system today does the same shallow thing:
append the raw free-text string to the campaign name. This gives the model no real
context about the system's conventions, and static category hints (e.g. "class or
role") assume D&D-ish vocabulary regardless of what's actually being played. With the
GameSystem registry (#133) storing a curated prompt_hint per system, prompts can
give the model real, consistent context instead of a bare string — while free-form
campaigns keep exactly today's behaviour (registry hint is simply absent).

Approach

Replace the bare (game_system) string interpolation with the registry's
prompt_hint (falling back to today's raw-string behaviour when the campaign has no
game_system_id) in each of these call sites:

  • Session summarisationsummarise(transcript, campaign_name, game_system, …)
    builds context = campaign_name + (f" ({game_system})" if game_system else "") at
    webapp/backend/app/services/audio_service.py:436-454. Swap in the resolved
    prompt_hint when a system is linked. Called from the audio pipeline Celery task
    (webapp/backend/app/tasks/reminder_tasks.py:1539,1618-1628) and the canonical-name
    re-summarise path (webapp/backend/app/routers/sessions.py:303-311).
  • Name generationgenerate_name_options(…, game_system, …) uses the identical
    interpolation (audio_service.py:759-780). The static, system-agnostic
    _NAME_CATEGORY_DESCRIPTION_HINTS dict (audio_service.py:163-218) gains optional
    per-system overrides — e.g. a pf2e player_character hint mentions
    ancestry/heritage/class instead of the generic "class or role" phrasing. Callers:
    session-scoped generator (routers/sessions.py:251-260), campaign planning cache
    (routers/campaigns.py:3129-3137), and the prefetch Celery task
    (webapp/backend/app/tasks/planning_tasks.py:72-84).
  • Lore proposal generation — the multi-pass extraction prompt currently does
    f"Game system: {campaign.game_system or 'Unknown'}"lore_service.py:720. Swap in
    the resolved system name/hint the same way.

Use the resolution helper from #133 to get the effective display string/hint per
campaign; no call site should read campaign.game_system directly once this lands —
they go through the helper so free-form and linked campaigns are handled uniformly.

Free-form campaigns (game_system_id IS NULL) keep today's exact prompt text — this is
purely additive richness for linked campaigns, not a rewrite of the free-text path.

Dependencies

  • #133 (GameSystem registry + campaign linkage) — provides prompt_hint on registry
    rows and the resolution helper this issue calls into.

Out of scope

  • Stat schemas, stats JSONB storage, or generate_structured_text/json_mode usage
    for stat blocks — that's issue 4 and issue 6 (the #130 upgrade).
  • Q&A (answer_question) — it doesn't use game_system today
    (audio_service.py:926-934) and this issue doesn't add it.
  • Any UI work — issue 2.
  • Writing/curating the actual prompt_hint copy for dnd5e/pf2e beyond a reasonable
    first draft — can be refined post-ship without further migration.

Acceptance criteria

  • summarise, generate_name_options, and the lore-extraction prompt all consume the
    registry prompt_hint (via the resolution helper) when the campaign has a linked
    system, and fall back to today's raw-string behaviour when it doesn't.
  • _NAME_CATEGORY_DESCRIPTION_HINTS supports a per-system override path without
    breaking the existing generic hints for free-form/unlinked campaigns.
  • No behaviour change for any campaign with game_system_id IS NULL — verified by
    existing tests continuing to pass unmodified.
  • New tests cover: a linked-dnd5e campaign produces a prompt containing the registry
    hint; a free-form campaign produces the unchanged legacy prompt string.
  • Reminder/planning Celery tasks that call these functions require no signature changes
    beyond what the resolution helper already provides.
## Motivation/Context Every LLM feature that references the game system today does the same shallow thing: append the raw free-text string to the campaign name. This gives the model no real context about the system's conventions, and static category hints (e.g. "class or role") assume D&D-ish vocabulary regardless of what's actually being played. With the `GameSystem` registry (#133) storing a curated `prompt_hint` per system, prompts can give the model real, consistent context instead of a bare string — while free-form campaigns keep exactly today's behaviour (registry hint is simply absent). ## Approach Replace the bare `(game_system)` string interpolation with the registry's `prompt_hint` (falling back to today's raw-string behaviour when the campaign has no `game_system_id`) in each of these call sites: - **Session summarisation** — `summarise(transcript, campaign_name, game_system, …)` builds `context = campaign_name + (f" ({game_system})" if game_system else "")` at `webapp/backend/app/services/audio_service.py:436-454`. Swap in the resolved `prompt_hint` when a system is linked. Called from the audio pipeline Celery task (`webapp/backend/app/tasks/reminder_tasks.py:1539,1618-1628`) and the canonical-name re-summarise path (`webapp/backend/app/routers/sessions.py:303-311`). - **Name generation** — `generate_name_options(…, game_system, …)` uses the identical interpolation (`audio_service.py:759-780`). The static, system-agnostic `_NAME_CATEGORY_DESCRIPTION_HINTS` dict (`audio_service.py:163-218`) gains optional per-system overrides — e.g. a pf2e `player_character` hint mentions ancestry/heritage/class instead of the generic "class or role" phrasing. Callers: session-scoped generator (`routers/sessions.py:251-260`), campaign planning cache (`routers/campaigns.py:3129-3137`), and the prefetch Celery task (`webapp/backend/app/tasks/planning_tasks.py:72-84`). - **Lore proposal generation** — the multi-pass extraction prompt currently does `f"Game system: {campaign.game_system or 'Unknown'}"` — `lore_service.py:720`. Swap in the resolved system name/hint the same way. Use the resolution helper from #133 to get the effective display string/hint per campaign; no call site should read `campaign.game_system` directly once this lands — they go through the helper so free-form and linked campaigns are handled uniformly. Free-form campaigns (`game_system_id IS NULL`) keep today's exact prompt text — this is purely additive richness for linked campaigns, not a rewrite of the free-text path. ## Dependencies - #133 (GameSystem registry + campaign linkage) — provides `prompt_hint` on registry rows and the resolution helper this issue calls into. ## Out of scope - Stat schemas, `stats` JSONB storage, or `generate_structured_text`/`json_mode` usage for stat blocks — that's issue 4 and issue 6 (the #130 upgrade). - Q&A (`answer_question`) — it doesn't use `game_system` today (`audio_service.py:926-934`) and this issue doesn't add it. - Any UI work — issue 2. - Writing/curating the actual `prompt_hint` copy for dnd5e/pf2e beyond a reasonable first draft — can be refined post-ship without further migration. ## Acceptance criteria - `summarise`, `generate_name_options`, and the lore-extraction prompt all consume the registry `prompt_hint` (via the resolution helper) when the campaign has a linked system, and fall back to today's raw-string behaviour when it doesn't. - `_NAME_CATEGORY_DESCRIPTION_HINTS` supports a per-system override path without breaking the existing generic hints for free-form/unlinked campaigns. - No behaviour change for any campaign with `game_system_id IS NULL` — verified by existing tests continuing to pass unmodified. - New tests cover: a linked-dnd5e campaign produces a prompt containing the registry hint; a free-form campaign produces the unchanged legacy prompt string. - Reminder/planning Celery tasks that call these functions require no signature changes beyond what the resolution helper already provides.
Author
Contributor

Picking this up on feat/137-system-aware-prompts (PR to target the feat/v3.9-game-aware-systems integration branch), in parallel with #140.

Approach: a new game_system_service.resolve_system_prompt_context(db, campaign) that returns the linked system's prompt_hint (from #133's registry) when linked, else the raw free-text game_system — wired into summarise, generate_name_options (+ optional per-system _NAME_CATEGORY_DESCRIPTION_HINTS overrides), and the lore-extraction prompt. Free-form/unlinked campaigns keep byte-identical prompts (existing tests unmodified). No bot API change.

Picking this up on `feat/137-system-aware-prompts` (PR to target the `feat/v3.9-game-aware-systems` integration branch), in parallel with #140. Approach: a new `game_system_service.resolve_system_prompt_context(db, campaign)` that returns the linked system's `prompt_hint` (from #133's registry) when linked, else the raw free-text `game_system` — wired into `summarise`, `generate_name_options` (+ optional per-system `_NAME_CATEGORY_DESCRIPTION_HINTS` overrides), and the lore-extraction prompt. Free-form/unlinked campaigns keep byte-identical prompts (existing tests unmodified). No bot API change.
Author
Contributor

Done and verified — merged into the integration branch via PR #208.

Verification (Docker, py3.12): full backend suite 632 passed (+14 new in test_system_aware_prompts.py), no regressions. Confirmed: linked-dnd5e campaigns get the registry prompt_hint in summarise / generate_name_options / lore-extraction prompts; free-form campaigns produce byte-identical legacy prompts; the pf2e per-category name-hint override fires only when linked.

Delivered: resolve_system_prompt_context(db, campaign) helper (safe async relationship handling via inspect().unloaded), wired into the named call sites; generate_name_options per-system category-hint overrides; a real db-lifecycle fix in the name-prefetch task (resolve context inside the task_session() block, capture system_key as a string before the session closes).

Follow-up flagged: two more raw-game_system prompt sites (NPC-fleshout backstory/statblock, which #142 touches next; and generate_session_title_suggestions) — left out of this issue's named scope, tracked for #142 / a small follow-up.

Closing; ships to main with the v3.9.0 release.

Done and verified — merged into the integration branch via PR #208. **Verification (Docker, py3.12):** full backend suite **632 passed** (+14 new in `test_system_aware_prompts.py`), no regressions. Confirmed: linked-dnd5e campaigns get the registry `prompt_hint` in `summarise` / `generate_name_options` / lore-extraction prompts; free-form campaigns produce byte-identical legacy prompts; the pf2e per-category name-hint override fires only when linked. **Delivered:** `resolve_system_prompt_context(db, campaign)` helper (safe async relationship handling via `inspect().unloaded`), wired into the named call sites; `generate_name_options` per-system category-hint overrides; a real db-lifecycle fix in the name-prefetch task (resolve context inside the `task_session()` block, capture `system_key` as a string before the session closes). **Follow-up flagged:** two more raw-`game_system` prompt sites (NPC-fleshout backstory/statblock, which #142 touches next; and `generate_session_title_suggestions`) — left out of this issue's named scope, tracked for #142 / a small follow-up. Closing; ships to `main` with the v3.9.0 release.
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#137
No description provided.