Closed
opened 2026-08-07 06:00:09 +00:00 by claude-bot
·
2 comments
No Branch/Tag specified
main
feat/1328-fill-it-in
feat/1328-fill-frontend
feat/1328-fill-backend
feat/9-foundry-push
fix/9-foundry-live
feat/9-foundry-push-frontend
renovate/celery-5.x
feat/5-foundry-connect
feat/9-foundry-push-backend
feat/9-foundry-push-module
feat/5-foundry-frontend
feat/5-foundry-backend
feat/5-foundry-module
release/v4.8.0-rc1
docs/d341-convert-draft
feat/958-update-to-version
feat/958-convert-sheet
feat/958-pathbuilder-import-frontend
fix/1360-heading-order
feat/958-pathbuilder-import-backend
feat/11-weekly-digest
feat/1341-auto-update
docs/d332-superseded
chore/1345-names-cleanup
feat/1342-mentioned-pages
feat/1328-recipes
fix/1346-pgdg-key
perf/1247-shared-recording-stream
chore/132-gitleaks-allowlist
release/v4.7.1
spike/foundry-module
release/v4.7.1-rc1
docs/decisions-d332-d337
fix/bot-certifi
chore/release-4.7.0
chore/release-4.7.0-rc4
chore/deps-4.7.0
chore/release-4.7.0-rc3
fix/1280-quote-must-say-it
fix/1257-names-row-squeeze
chore/release-4.7.0-rc2
fix/1245-pg-upgrade-count
chore/release-4.7.0-rc1
feat/1257-names-in-prep
feat/1257-be
feat/1257-fe
feat/1257-rating-fixes
design/1328-fill-it-in
feat/1257-name-splits
fix/1326-redis-503
feat/1280-fact-suggestions
feat/1280-be
feat/1280-fe
design/1280-fact-suggestions
feat/1279-infobox
feat/1279-be
feat/1279-fe
feat/1321-shelf-copies
design/1279-infobox
feat/1243-table-find
feat/1257-who-rated
feat/1243-prep-drawer
feat/1243-sheet-pickers
design/1243-browse-in-place
feat/1315-hotwords-default
feat/1242-system-filters
feat/1257-more-name-styles
feat/506-leg3-hotwords
design/1242-system-filters
fix/1302-packs-polish
feat/1241-rules-library
design/1241-rules-library
feat/1245-postgres-18
integ/4.6.2
fix/1263-map-ui-2
fix/1263-map-server
fix/1263-map-ui-1
fix/1261-editor-link-menu
fix/1229-axe-4.14-name-mismatch
renovate/axllent-mailpit-1.x
renovate/eslint-monorepo
renovate/authlib-1.x
chore/release-4.6.0
chore/release-4.6.0-rc2
fix/460-rc2-screens
fix/460-rc2-server
fix/release-prerelease-exit
chore/release-4.6.0-rc1
feat/125-campaign-templates
feat/126-calendar
feat/1181-pictures-and-files
feat/1190-event-people
integ/4.6.0
feat/1191-line-of-the-night
feat/1200-starter-packs
chore/bot-multidict-and-playwright-group
fix/1202-this-week-answered-count
fix/122-124-leftovers
fix/1178-get-routes-that-write
fix/1203-journeys-check-names
design/125-campaign-templates
feat/126-calendar-engine
design/126-in-world-calendar
fix/1189-cited-stamp-in-range
fix/1189-v4-looser-check
fix/1189-richer-beats
fix/1113-export-bind-lists
fix/1154-release-notes-by-file
chore/release-4.5.0
chore/release-4.5.0-rc12
fix/1135-wiki-markup-as-text
fix/demo-usage-cost-in-window
chore/release-4.5.0-rc11
fix/1121-sheet-consistency
fix/1121-sheet-5e-terms-and-card-hp
copy/1122-busiest-pages
fix/1121-relevant-choices
fix/1121-wiki-create-npc-save-sheet-links
fix/1029-menu-stays-on-screen
docs/prototype-transcript-bin-wording
fix/1023-editor-save-row-back-scroll
fix/1020-search-exact-first
fix/1020-table-prep
fix/1020-shell-wordmark-footer-bell
fix/1020-wiki-article-party
fix/872-admin-kept-until-phone-tables
feat/l13-milestone-level-waiting
fix/1000-1001-bell-older-setup-context
fix/999-pronouns-in-prompts
fix/l19-other-class-feats
fix/1010-transcript-window-ordinary-confirm
docs/d254-d269-decisions
fix/702-member-search
fix/680-transcript-bin
fix/993-unidentified-prompts
fix/841-redis-timeouts
fix/990-binned-not-exported
fix/830-export-prep-shelf
fix/deps-audit-2026-10-01
fix/877-title-reads
fix/798-audio-received-at
build/lane11-17-j7
build/lane11-16-levelup
build/lane11-14-seams
build/lane11-14-seed
build/lane11-15-start
build/lane11-13-old-block-out
fix/980-review-r3
build/lane11-12-block
build/lane11-8-packs
fix/demo-seed-canonical-names
build/lane11-11-hot-half
docs/l11-decisions
build/lane11-7-convert
build/lane11-10-editing
fix/npm-audit-brace-expansion
build/lane11-9-sheet-page
fix/bot-pyjwt
build/lane11-6-content
fix/lane11-rank-names
build/lane11-2b-language
build/lane11-4-js-evaluator
build/lane11-3-server
build/lane11-5a-pf2e
build/lane11-5b-5e
feat/design-admin
build/lane11-2-evaluator
build/lane11-1-sheet-pieces
build/lane11-0-drawing
fix/943-quest-objectives-fk
fix/prepsheet-shelf-flake
fix/945-leftovers
build/demo-gm-copy
fix/901-email-landings
build/lane10-8-journeys
build/demo-seed-lanes-8-10
fix/887-gm-own-confirmation
build/lane9-5-j1-admin
fix/918-audience-click
fix/useasync-overtaken
ci/939-critical-path
test/937-backend-suite-speed
chore/claude-orchestration
fix/924-xp-gm-only
fix/session-stale-poll
docs/decisions-d239-d242
fix/930-pypdf
build/lane8-journeys
build/925-quest-editor
fix/894-private-page-pictures
build/lane8-p4-drafts
build/lane8-p7-map
build/lane8-p6-handouts-rules
fix/wiki-fixture-names
build/lane10-d227-test-usage
fix/rsvp-horizon-test-tuesday
fix/media-volume-owner
build/lane8-p3-editor
fix/912-unidentified-rename
build/lane8-p5-party-quests
build/lane8-handout-takeback
build/lane10-pickers
build/lane8-p2-article
build/lane8-6-map
build/lane10-7b-email-pages
build/lane8-5-handouts
build/lane10-7a-test-the-bot-pages
build/lane8-p1-pages
build/lane8-4-quests
build/lane8-2b-feeds
build/lane8-3-parties
build/lane8-2a-links
build/lane10-5-commands
build/lane9-6-seed
build/lane10-4b-reactions
build/lane9-4d-pages
build/lane9-4c-pages
build/lane10-4a-posts
build/lane10-3-email
fix/891-web-fonts
build/lane9-4b-pages
build/lane10-6-test-the-bot
build/lane10-2-notify-out
build/lane8-1-wiki-text
build/lane9-4a-frame
design/lane10-drawings
fix/j6-budget
fix/j5-phone-flake
fix/869-test-leaks
fix/843-record-start-followups
build/lane7-all
build/lane6-all
build/lane9-b2-server
chore/changelog-fragments
docs/decisions-d218
build/lane9-a-owner-overview
fix/rule-card-level-label
fix/834-retention-semantics
fix/o3c-record-without-announcement
docs/decisions-d179
docs/decisions-d131-d170
fix/851-commit-before-response
build/lane6-j3-seed
build/lane9-b1-d67-identity
fix/841-blocking-loop
build/lane9-c-backups-audit
fix/vote-race-and-start-commit
fix/recording-announcement
fix/proposal-read-leak
fix/last-admin-guard
build/lane6-p4c-planning
build/lane6-p4b-margin
build/lane6-p4a-sheet
build/lane5-j4-seed
build/lane6-base
build/lane7-pages-talk
build/lane5-pages
build/lane7-pages-safety
build/lane7-p3-safety
build/lane7-p2-notify
build/lane6-p3-planning
build/lane6-p2-history
build/lane6-p1b-items
build/lane7-p1-threads
build/lane5-p6-faces
build/lane6-p1a-sheet
build/lane5-p5-find
build/lane5-p4a-board
build/lane5-p4b-scenes
build/lane5-p3-live
build/lane5-base
build/lane5-p2-cards
docs/lane5-decisions
build/review-r2-wide
fix/review-r1
build/lane5-p1-scenes
build/demo-seed
build/lane4-journeys-j5
build/lane4-page-review-b
build/lane4-page-review-a
build/lane4-page-read
build/lane4-journeys
build/lane4-page-sess2
build/lane4-page-sess1
build/lane4-notes
build/lane4-discord-on-approval
build/lane4-scheduling
build/lane4-versions
build/760-session-number
build/lane4-decisions
build/797-erase-no-regenerate
build/test-race-audit
build/lane3-completions
build/794-dark-only
build/796-titles-scroll
build/791-retention-anchor
build/a11y-sweep
build/shell-followups
build/retention-preview
build/753-backup-readback
docs/d91-d93
build/lane3-j1
build/ci-harness-paths-selftest
fix/useauth-test-race
build/ci-harness-paths
build/777-ai-settings-read
build/lane3-settings-c
build/lane3-settings-b
build/lane3-j6
build/lane3-sessionform
build/lane3-settings-a
build/lane3-members
build/lane3-thisweek
build/lane3-story
chore/87-align-python-3.12
v4.8.0-rc1
v4.7.1
v4.7.1-rc1
v4.7.0
v4.7.0-rc4
v4.7.0-rc3
v4.7.0-rc2
v4.7.0-rc1
v4.6.2
v4.6.2-rc4
v4.6.2-rc3
v4.6.2-rc2
v4.6.2-rc1
v4.6.1
v4.6.1-rc1
v4.6.0
v4.6.0-rc2
v4.6.0-rc1
v4.5.3
v4.5.3-rc1
v4.5.2
v4.5.2-rc1
v4.5.1
v4.5.1-rc2
v4.5.1-rc1
v4.5.0
v4.5.0-rc12
v4.5.0-rc11
v4.5.0-rc10
v4.5.0-rc9
v4.5.0-rc8
v4.5.0-rc7
v4.5.0-rc6
v4.5.0-rc5
v4.5.0-rc4
v4.5.0-rc3
v4.5.0-rc2
v4.5.0-rc1
v4.4.1
v4.4.0
v4.3.0
v4.2.3
v4.2.2
v4.2.1
v4.2.0
v4.1.2
v4.1.1
v4.1.0
v4.0.1
v4.0.0
v3.11.4
v3.11.3
v3.11.2
v3.11.1
v3.11.0
v3.10.0
v3.9.1
v3.9.0
v3.8.0
v3.7.0
v3.6.1
v3.6.0
v3.5.0
v3.4.1
v3.4.0
v3.3.0
v3.2.0
v3.1.0
v3.0.0
v2.1.0
v2.0.0
v1.1.0
v1.0.0
v0.12.0
v0.11.0
v0.10.0
v0.9.1
v0.3.3
v0.9.0
v0.8.0
v0.3.2
v0.3.1
v0.3.0
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.2.1
v0.2.0
v0.1.1
v0.1.0
Labels
Clear labels
backend
Issue related to the backend
bot
Issue related to the bot
bug
Something is not working
duplicate
This issue or pull request already exists
enhancement
New feature
frontend
Issue related to the frontend
help wanted
Need some help
invalid
Something is wrong
question
More information is needed
recording
Issue related to the recording system
security
Security hardening / vulnerability work
wontfix
This won't be fixed
No labels
backend
bot
bug
duplicate
enhancement
frontend
help wanted
invalid
question
recording
security
wontfix
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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#294
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Fix direction 4 from #287, split out so it does not ride along with that issue's semantic changes. #287 is closed; this is the structural half it deliberately deferred.
Why it was deferred, and why it still matters
#287 made the "no LLM configured" policy documented —
settings_servicenow names the four legitimate policies andrequire_llm_configimplements the default one. But documentation is a convention, and conventions drift. The reason eight behaviours appeared in the first place is that each task hand-writes its own preamble, so there is nothing structural stopping a ninth.The decorator is what would make the policy uniform by construction rather than by discipline.
The duplication, measured against current
main(0a8910e)reminder_tasks.pyis 4,676 lines. Within it:asyncio.run(...)sync→async wrappersasync with task_session() as db:blocksuuid.UUID(x)/except ValueError/ log /returnparse guardreminder_tasks.pyandplanning_tasks.pySix tasks share essentially one skeleton end to end:
_generate_lore_entry_summary_asyncrun_lore_entry_draft_generationrun_workbench_generation_core_propose_lore_relationships_async_generate_journal_entry_async_generate_session_title_suggestions_asyncEvery one of them does, in order:
asyncio.runwrapper around an inner_asyncValueErrorasync with task_session() as dbdb.get(Model, id), log and return ifNoneNonetry/except, log, swallowSteps 1–6 are boilerplate. Only step 7's body differs.
Shape
LlmPolicymaps onto the four policies already documented insettings_service's module docstring — so the decorator becomes the enforcement point for a taxonomy that currently exists only in prose.Constraints worth stating up front
process_audiois not a candidate.max_retries=0, its owntry/exceptrecordsaudio_processing_status, and it resolves Whisper and VAD config alongside the LLM. Leave it alone.lore_chunk_extract,lore_deduplicate_extracts,lore_match_category) have a bespokeon_failurerecordinglore_generation_statusand a retry handler that special-casesLlmNotConfiguredError. The decorator must not flatten that — either compose with it or exclude them.Acceptance
@llm_taskexists, with the policy enum tied tosettings_service's documented fourreminder_tasks.py, and adding a new LLM task no longer requires copying a preambleNot in scope
Splitting
reminder_tasks.py(4,676 lines, and no longer only about reminders). Worth doing eventually; a decorator that shrinks it first makes that split easier to reason about.extractingforever #287Picking this up on
feat/v4-deterministic-attribution.Before implementing I read all six tasks as they stand now (
reminder_tasks.pyis 4,930 lines on the branch, not 4,676 — the v4 work grew it). One premise in the issue is wrong in a way that changes the design, so recording that here rather than silently building something else.The six tasks are not uniform in ordering, and that is load-bearing
The issue says every one of them does steps 1–7 "in order". They don't. The LLM resolution sits at a different point in each:
_generate_lore_entry_summary_asyncif not entry.body: return_generate_journal_entry_asyncif not session.summary: return, "public note already exists" guard_generate_session_title_suggestions_async_propose_lore_relationships_asyncrun_workbench_generation_coreif tool is None: mark_failed("Unknown workbench tool")run_lore_entry_draft_generationA decorator that resolves the config at the top and hands the body an
llm_cfgreorders those guards. That is a real behaviour change, not a cosmetic one:LLM not configured, skipping entry …at WARNING. Same for every session with no summary and every session that already has a journal note. On a no-LLM install that inverts the log from "nothing to do" to a standing warning — the exact noise problem #287 set out to fix.tool_idwould start reporting "LLM endpoint not configured" to the GM instead of "Unknown workbench tool". That one is user-visible and simply wrong.Design: the body decides when, the decorator decides what happens on None
So the config is passed as a zero-argument async resolver, not a value:
llm()never returnsNone— it either yields a config or executes the declared policy (log-and-return forSKIP,LlmNotConfiguredErrorforREQUIRE, the row recorder forRECORD_ON_ROW). That is stronger than the original sketch, not weaker: withllm_cfghanded in, a body could still forget to branch onNone. Here there is noNonebranch to get wrong, and ordering is preserved exactly. Uniform by construction, which was the point.Two corrections to scope
Policy 2 (
RECORD_ON_ROW) does not generalise. "The row" and the recording differ per task —draft.status/draft.last_errorversusgeneration_result_service.mark_failed(...)with a task-specific message. And both of those tasks' cores (run_lore_entry_draft_generation,run_workbench_generation_core) are called directly by 20-odd tests asf(db, id), so their signatures are pinned and a decorator cannot reach inside them. The enum keeps the member and takes anon_missingcallback, but for these two the decorator only absorbs the wrapper preamble (parse + session); their policy-2 branch stays where it is. Adding this to the "constraints" list alongsideprocess_audioand the lore pipeline.The net-line-reduction acceptance criterion is unlikely to be met by six tasks alone. The preamble is ~20 lines per task, so six adopters save ~120 lines against a decorator that costs about the same once documented. I'm adopting a seventh (
_update_campaign_storyline_async— same preamble, and it makes the kwargs path real) which helps, but the honest value here is the enforcement point, not the line count.reminder_tasks.pygets meaningfully shorter only when the other ~20task_sessionblocks adopt the preamble half, and most of those take no id at all, so their preamble is a single line and a decorator would not pay. I'd suggest striking that criterion rather than chasing it.planning_tasks.pyis excluded: both of its LLM sites sit inside a Redis lock and return adict, a different skeleton entirely.Done in
bcd5187onfeat/v4-deterministic-attribution.What shipped
app/tasks/llm_task.py(212 lines) —LlmPolicy(REQUIRE/RECORD_ON_ROW/SKIP, with policy 4 deliberately absent and a comment saying why: treatingNoneas data to describe is an endpoint concern, never a task one), plus two layered decorators.task_bodyabsorbs parse-id →task_session()→ optional row load.llm_taskadds the deferred resolver.Seven tasks adopted it — the six from the issue plus
_update_campaign_storyline_async, which has the identical preamble and exercises the**kwargspath (full_regenerate).Three things that would have broken this quietly, recorded for whoever touches it next
task_sessionis imported inside the wrapper, not at module level. Seventeen tests patchapp.database.task_sessionto reuse their transactional session; a module-level import binds the original at import time and every one of them fails somewhere confusing.logging.getLogger(func.__module__), not the decorator's own logger, so records still originate fromapp.tasks.reminder_tasks.except Exception. A body wrapping its LLM work in a broad except would otherwise swallow the policy and never run it — the one failure mode that would make this silently worse than the duplicated code it replaces. There's a regression test for it. None of the seven converted bodies actually had that shape, but the next one might.One design change beyond the issue:
llmis a required keyword with no default. Defaulting it toSKIPwould hand out policy 3 to any call site that omitted the argument, which rebuilds #287's drift one level up with fewer places to look for it. Pinned bytest_omitting_the_policy_is_an_error_rather_than_a_silent_default.Verification: 1,130 passing before → 1,142 after, with 12 new decorator tests and zero edits to existing tests.
ruff format+ruff checkclean. Also importedapp.tasks.reminder_tasksstandalone in Celery's include order — 42 tasks register — to confirm theloads=model imports hoisted to module scope don't create a cycle. They can't:app.models.*import onlyapp.databaseand sqlalchemy. The issue's warning about function-local imports holds for the service imports, which stayed local.Acceptance, honestly
@llm_taskexists, policy enum tied tosettings_service's documented fourreminder_tasks.py4,930 → 4,816 (−114), against 212 lines forllm_task.py. As flagged above, I don't think it's worth chasing: six adopters were never going to pay for a documented module, and the remainingtask_sessionblocks mostly take no id, so their preamble is one line a decorator wouldn't improve. The value delivered is the second half of that criterion — adding a new LLM task no longer means copying a preamble, and the policy has one implementation instead of seven. Ticking the first half would mean either inlining the documentation or converting tasks that don't benefit.Closing. The file split the issue puts out of scope is now easier to reason about, if you want an issue for it.