[GM Workbench] Generation scratchpad/history (GenerationResult model) #138

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

Motivation / Context

Today, anything generated but not saved is lost when the 2-hour Redis TTL on name-option caching expires or the tab closes (webapp/backend/app/tasks/planning_tasks.py:11-12). There is no persistence of generated-but-unsaved options. GMs generate in bursts during prep and want to come back to results later — pick between three loot parcels, revisit a rumor they didn't use yet, re-open a backstory draft. No generation-history model exists anywhere in the schema today (confirmed: grep for loot/quest_log/generation in webapp/backend/app returns nothing relevant).

This is issue 3 of 3 foundational items, completing the enabling layer before individual Workbench tools ship. It's also the async-status carrier for issue #136's sync/Celery split, and — per the report's hosted-service section (§6) — the natural home for future token-usage metering.

Approach

  • New model GenerationResult (§5.4), following the campaign-scoped inline-router precedent already used for planning arcs/threads (webapp/backend/app/routers/campaigns.py:3172-3390):
    • id, campaign_id (FK CASCADE, indexed), tool_id (Text), params (JSONB), output (JSONB), status (pending/ready/failed — use raw SQL for the enum per this repo's Alembic convention, not op.create_table() with sa.Enum directly, to avoid spurious CREATE TYPE), error, pinned (bool), session_id (nullable FK), created_by, created_at.
    • Add a token_usage JSONB column from day one (§6) — provider responses include usage data, and this is the cheapest point to start capturing it for future metering even though quota enforcement itself is out of scope here.
  • Migration in webapp/backend/alembic/ following existing conventions for JSONB + FK-cascade columns (see the PlotThread/CampaignArc migrations for the inline-router-backed model precedent).
  • Endpoints, campaign-scoped, GM-only:
    • GET /api/campaigns/{campaign_id}/workbench/history — list, filterable by tool_id, pinned.
    • Pin/unpin and delete endpoints for individual GenerationResult rows.
  • Async status lifecycle: the Celery-backed tools registered in issue #136 write pending on enqueue and ready/failed on completion into this table — the same generating → ready/failed shape the existing draft pipeline uses (draft_service.py, reminder_tasks.py:2051-2124). The frontend polls the history/status endpoint, mirroring how WikiDraftReview.jsx already polls draft status.
  • Retention cleanup: a Celery beat task that deletes unpinned GenerationResult rows older than ~30 days, added alongside the existing beat schedule (find the existing periodic-cleanup task pattern in reminder_tasks.py or the Celery beat config and follow it).
  • Right-rail UI: minimal scratchpad/history panel — list view filtered by tool, pin/unpin, delete, "re-open into the panel" (loads params/output back into the generation form). This can be a simple list for this issue; the full two-pane workbench layout is issue #153.

Dependencies

  • #134 (Extract LLM core) — indirect; this model doesn't call the LLM directly.
  • #136 (Generalized workbench endpoint + tool registry) — direct and required. The POST /workbench/{tool_id}/generate endpoint needs GenerationResult to exist so it has somewhere to write sync results and async status. In practice these two are implemented back-to-back.
  • Every later Workbench tool issue (#141 and beyond) writes into and reads from this table as its scratchpad.

Out of scope

  • Per-campaign generation quotas / rate limiting — token_usage is captured here for future metering, but enforcing quotas against it is a separate hosted-service concern (§6), not built in this issue.
  • The full two-pane workbench UX (tool palette, unified GeneratorPanel, right-rail redesign) — issue #153. This issue only needs a functional, minimal history list.
  • Per-tool model override / model tiering (§6) — noted as a future registry field, not implemented here.

Acceptance criteria

  • GenerationResult model + migration exist with all fields above, including token_usage JSONB.
  • GET /api/campaigns/{campaign_id}/workbench/history returns campaign-scoped results, filterable by tool_id and pinned, GM-only.
  • Pin/unpin and delete work and are GM-only.
  • A Celery-backed tool's pending → ready/failed transitions are visible via the history endpoint (verified against at least the names tool once wired through #136, or a stub tool for this issue's own testing).
  • Retention cleanup task deletes unpinned rows older than the configured threshold and is registered in Celery beat; pinned rows are never auto-deleted.
  • Minimal frontend list view exists showing history entries with pin/delete/re-open actions.
## Motivation / Context Today, anything generated but not saved is lost when the 2-hour Redis TTL on name-option caching expires or the tab closes (`webapp/backend/app/tasks/planning_tasks.py:11-12`). There is no persistence of generated-but-unsaved options. GMs generate in bursts during prep and want to come back to results later — pick between three loot parcels, revisit a rumor they didn't use yet, re-open a backstory draft. No generation-history model exists anywhere in the schema today (confirmed: grep for loot/quest_log/generation in `webapp/backend/app` returns nothing relevant). This is issue 3 of 3 foundational items, completing the enabling layer before individual Workbench tools ship. It's also the async-status carrier for issue #136's sync/Celery split, and — per the report's hosted-service section (§6) — the natural home for future token-usage metering. ## Approach - **New model `GenerationResult`** (§5.4), following the campaign-scoped inline-router precedent already used for planning arcs/threads (`webapp/backend/app/routers/campaigns.py:3172-3390`): - `id`, `campaign_id` (FK CASCADE, indexed), `tool_id` (Text), `params` (JSONB), `output` (JSONB), `status` (`pending`/`ready`/`failed` — use raw SQL for the enum per this repo's Alembic convention, not `op.create_table()` with `sa.Enum` directly, to avoid spurious `CREATE TYPE`), `error`, `pinned` (bool), `session_id` (nullable FK), `created_by`, `created_at`. - Add a `token_usage` JSONB column from day one (§6) — provider responses include usage data, and this is the cheapest point to start capturing it for future metering even though quota enforcement itself is out of scope here. - **Migration** in `webapp/backend/alembic/` following existing conventions for JSONB + FK-cascade columns (see the `PlotThread`/`CampaignArc` migrations for the inline-router-backed model precedent). - **Endpoints**, campaign-scoped, GM-only: - `GET /api/campaigns/{campaign_id}/workbench/history` — list, filterable by `tool_id`, `pinned`. - Pin/unpin and delete endpoints for individual `GenerationResult` rows. - **Async status lifecycle**: the Celery-backed tools registered in issue #136 write `pending` on enqueue and `ready`/`failed` on completion into this table — the same `generating → ready/failed` shape the existing draft pipeline uses (`draft_service.py`, `reminder_tasks.py:2051-2124`). The frontend polls the history/status endpoint, mirroring how `WikiDraftReview.jsx` already polls draft status. - **Retention cleanup**: a Celery beat task that deletes unpinned `GenerationResult` rows older than ~30 days, added alongside the existing beat schedule (find the existing periodic-cleanup task pattern in `reminder_tasks.py` or the Celery beat config and follow it). - **Right-rail UI**: minimal scratchpad/history panel — list view filtered by tool, pin/unpin, delete, "re-open into the panel" (loads `params`/`output` back into the generation form). This can be a simple list for this issue; the full two-pane workbench layout is issue #153. ## Dependencies - **#134** (Extract LLM core) — indirect; this model doesn't call the LLM directly. - **#136** (Generalized workbench endpoint + tool registry) — direct and required. The `POST /workbench/{tool_id}/generate` endpoint needs `GenerationResult` to exist so it has somewhere to write sync results and async status. In practice these two are implemented back-to-back. - Every later Workbench tool issue (#141 and beyond) writes into and reads from this table as its scratchpad. ## Out of scope - Per-campaign generation quotas / rate limiting — `token_usage` is captured here for future metering, but enforcing quotas against it is a separate hosted-service concern (§6), not built in this issue. - The full two-pane workbench UX (tool palette, unified `GeneratorPanel`, right-rail redesign) — issue #153. This issue only needs a functional, minimal history list. - Per-tool model override / model tiering (§6) — noted as a future registry field, not implemented here. ## Acceptance criteria - `GenerationResult` model + migration exist with all fields above, including `token_usage` JSONB. - `GET /api/campaigns/{campaign_id}/workbench/history` returns campaign-scoped results, filterable by `tool_id` and `pinned`, GM-only. - Pin/unpin and delete work and are GM-only. - A Celery-backed tool's `pending → ready/failed` transitions are visible via the history endpoint (verified against at least the names tool once wired through #136, or a stub tool for this issue's own testing). - Retention cleanup task deletes unpinned rows older than the configured threshold and is registered in Celery beat; pinned rows are never auto-deleted. - Minimal frontend list view exists showing history entries with pin/delete/re-open actions.
Author
Contributor

Picking this up (foundation item 3/3) on feat/138-generation-scratchpad → PR onto feat/v3.10-gm-workbench. Building the GenerationResult model + migration + campaign-scoped history/pin/delete endpoints + the pending → ready/failed lifecycle helpers (which #136's sync/async tools write into) + a retention beat task + a minimal history list. Built after #134 (merged); #136 wires the real tools into this next.

Picking this up (foundation item 3/3) on `feat/138-generation-scratchpad` → PR onto `feat/v3.10-gm-workbench`. Building the `GenerationResult` model + migration + campaign-scoped history/pin/delete endpoints + the `pending → ready/failed` lifecycle helpers (which #136's sync/async tools write into) + a retention beat task + a minimal history list. Built after #134 (merged); #136 wires the real tools into this next.
Author
Contributor

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

Verification (Docker): backend 674 tests (+15 test_workbench_history.py), migration f0a1b2c3d4e5 up/down/up clean; frontend 345 tests (+5) + build; ruff + eslint clean.

Delivered: GenerationResult model + campaign-scoped GM-only history/pin/delete endpoints + lifecycle helpers (create_result/mark_ready/mark_failed) + delete_expired + a daily prune_generation_results beat task (pinned rows never swept) + token_usage captured for future metering + a minimal GenerationHistory list (pin/delete/re-open). All three foundation items (#134/#138 + #136 next) are the enabling layer before the tools.

Closing; ships to main with the v3.10.0 release. Next: #136 wires the tool registry + /workbench/{tool_id}/generate endpoint into this scratchpad (names as the first registered tool).

Done and verified — merged into the integration branch via PR #217. **Verification (Docker):** backend **674 tests** (+15 `test_workbench_history.py`), migration `f0a1b2c3d4e5` up/down/up clean; frontend **345 tests** (+5) + build; ruff + eslint clean. **Delivered:** `GenerationResult` model + campaign-scoped GM-only history/pin/delete endpoints + lifecycle helpers (`create_result`/`mark_ready`/`mark_failed`) + `delete_expired` + a daily `prune_generation_results` beat task (pinned rows never swept) + `token_usage` captured for future metering + a minimal `GenerationHistory` list (pin/delete/re-open). All three foundation items (#134/#138 + #136 next) are the enabling layer before the tools. Closing; ships to `main` with the v3.10.0 release. Next: **#136** wires the tool registry + `/workbench/{tool_id}/generate` endpoint into this scratchpad (names as the first registered tool).
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#138
No description provided.