product

JQ as canon keeper — design doc

Status: SHIPPED — Phases 1–3 (as of 2026-07-01; PRs incl. #957–#960). The implementation lives in canon-mode.ts (read context + canon_health audit + CANON_WRITE_TOOLS → versioned applyDelta

  • cross-story canon_consistency + universe audit), wired into chat-service.ts (scope detection → context injection → tool registration → executeCanonTool dispatch, with a gpt-4o override for canon mode), scoped via migration 417_chat_canon_scope.sql, and opened from the season/universe pages via the jq:open-canon event handled in Chatbot.tsx. The rest of this file is the original design (kept for rationale). Known remaining gaps: settings/themes/voice-rules aren't editable via tools yet; editing is season-scope only (universe-level edits are describe-only). (Closed 2026-07-02: canon_health now also runs the prose-vs-bible checks — unmentioned characters, first-appearance drift, orphan relationship refs — via the read-layer cores character-mention-index.ts + character-relationship-graph.ts; proseCanonFindings + mergeCanonHealth in canon-mode.ts, golden-tested in canon-health.test.ts.)

Original design follows. Make JQ (the AI companion) a conversational organizer over story/universe canon — universes, characters, settings, motifs, planted threads/storylines — so a writer manages their world by talking to JQ instead of hand-editing JSON in admin modals. Background: JQ chat = chat-service.ts; canon = story-bible.ts + universe-canon.ts.

Thesis: JQ is already ~80% of the way there

This is more tools on an existing agent, not new infrastructure:

  • JQ is already a function-calling agent. chat-service.ts runs a real OpenAI tool loop (call → executeToolCall → append role:'tool' result → re-submit), with per-user conversations (chat_conversations / chat_messages, migration 035) and a role-gated tool surface.
  • JQ already writes canon. It exposes spawn_universe_batch + spawn_show_in_universe (super-admin), which already create story_universes and story_seasons. So JQ already touches the world it would manage.
  • The canon layer is unusually agent-ready — every mutation is a clean service function, and the bible already has a safe-edit protocol: applyDelta() is additive + update-only, versioned, and audit-trailed (snapshots the prior version with a commit message + author into season_story_bible_versions). That is exactly what you want an LLM touching canon to use — conversational edits become reviewable, reversible commits, never silent overwrites. There's even a pre-existing propose → review → apply queue (episode_take_bible_deltas) JQ can reuse.

What JQ would manage (the data model already speaks this language)

ConceptBackingLevel
Universestory_universes (+ code registry constants/story-universes.ts)universe
CharactersBibleCharacter (arc · voice_brief · inner_life · relationships · status · verbal_tics)per-season bible and universe_characters library
Settings / locationsBibleSetting (sensory_palette · rules)per-season bible and universe_places
Storylines / threadsplanted_threads (status planted→developing→paid_off→dropped, payoff target) + motifs + character arcper-season and universe canon
Themes / voice / tonethemes, voice_rules, tone_rules, do_not_useboth

"Storylines" aren't a separate table — they're planted_threads (the load-bearing one) plus motifs and character arcs. A universe-wide arc lives in story_universes.canon.planted_threads; a per-novel one in the bible.

Architecture: a JQ "canon mode"

A conversation scoped to a universe or a season, with that canon loaded into context, and canon tools enabled.

  1. Scope — add nullable universe_key / season_id to chat_conversations (migration 417). When set, JQ is "in canon mode" for that world.
  2. Context — inject the scoped canon into the system prompt (reuse buildBiblePromptBlock / the universe canon serializer) so JQ reasons over the real bible, not a guess. Token-budgeted like the chapter prompt.
  3. Tools — register canon tools (below) gated on ownership of the scoped universe/season, not just super-admin.
  4. Safe writes — JQ proposes a BibleDelta, previews it in chat, and on confirm calls applyDelta() → a new versioned bible with a JQ-authored commit message. Reuses the existing delta-review UX; nothing is ever silently overwritten.

Candidate tools (thin wrappers over existing services)

Read (Phase 1):

  • describe_canon(scope) → current bible / universe canon summary
  • list_characters / list_settings / list_threads(status?) → from getCurrentBible / listUniverseCharacters / listUniversePlaces
  • canon_health(scope) → orphaned threads, arc-less characters, prose-vs-bible drift

Write (Phase 2), all via the versioned/audited path:

  • add_character / update_character (arc, status, voice, relationships) → applyDelta
  • add_thread / update_thread (status, payoff, summary) → applyDelta
  • add_setting / add_motif / update_themeapplyDelta or commitBible
  • upsert_universe_character / upsert_universe_placeuniverse-canon.ts
  • approve_delta / reject_delta → the existing chapter-delta queue

The three real decisions (recommendations)

  1. Model. Tools are currently OpenAI-only, pinned gpt-4o-mini in the LLM shim (Anthropic tool-calling isn't implemented; fallback is disabled when tools are set). gpt-4o-mini is fine for "add a task," likely too weak for nuanced canon reasoning. Rec: use gpt-4o for canon mode now; teach the shim Anthropic tool-calling later so JQ can use Sonnet (stronger at this).
  2. Permissions. JQ's tools are super-admin-gated and act "for the calling user." Canon mode needs a per-resource ownership check — the caller may only edit canon for a universe/season they own or admin. Small addition at tool-dispatch time in executeToolCall.
  3. Propose vs. auto-apply. Default to propose + confirm (a previewable delta), reusing the delta-review flow — never let JQ silently rewrite canon. As trust grows, allow confident low-risk edits (e.g. update_thread.status) to auto-commit.

The "organizer" payoff (Phase 3)

The actual ask — JQ keeping canon managed, not just editing it on command:

  • Canon health: flag planted threads with no payoff, characters with no arc, settings that appear in prose but aren't in the bible, statuses that contradict the latest chapter.
  • Consistency guard: warn when an edit would contradict the universe's mandatory canon, or when two seasons in a universe diverge on a shared character/place.
  • Proactive nudges (via the existing JQ routines): "3 threads in <story> are still 'planted' past their payoff chapter — want to resolve or drop them?"

Phasing

  1. Read-only canon mode — scope a chat to a universe/season, load canon, add the list_* / describe_canon / canon_health read tools. Cheapest, zero write-risk, immediately useful as a canon reference you can interrogate. (Schema: conversation scope columns.)
  2. Conversational edits — the write tools via applyDelta / upsert, confirm-before-apply, each a versioned commit. (Ownership checks + a stronger model for canon mode.)
  3. Organizer intelligence — canon-health surfacing + cross-story/universe consistency + proactive routine nudges.

Schema / code touch points

  • Migration 417: chat_conversations.universe_key TEXT, season_id UUID (nullable scope).
  • chat-service.ts: a canon-mode branch of getSystemPrompt (inject scoped canon), new tool defs + executeToolCall handlers, ownership guard, model override to gpt-4o for canon mode.
  • Reuse wholesale: story-bible.ts (getCurrentBible, applyDelta, commitBible), universe-canon.ts (upsert*/list*), story-bible-deltas.ts (approve/reject), buildBiblePromptBlock.
  • Frontend: a way to open JQ "about this universe/season" from the season / universe editor (sets the conversation scope), + render proposed-delta preview cards inline in the chat (Chatbot.tsx).

Open questions / risks

  • LLM editing canon is high-trust. The versioned/propose-confirm design is the mitigation; never expose a destructive commitBible that bypasses review to the model without confirmation.
  • Universe canon is partly code-defined (constants/story-universes.ts for Turing Logs / Deep Cut) and partly DB (story_universes + the canon-library tables). JQ should edit the DB-backed surfaces; code-registry universes are read-only until that registry fully migrates to the DB.
  • Token budget: a full bible + universe canon can be large; reuse the existing trim-priority budgeting from buildBiblePromptBlock.
  • Scope drift: JQ should confirm universe-level vs season-level before a write ("change this for the whole universe, or just this story?").

Why it's worth it

JQ becomes the connective tissue between the writer and the whole story→script→movie stack — and a well-tended bible directly improves everything downstream that reads it (the Reverse Screenplay Engine, Scene Studio, character-voice casting, audio). It also advances the Odessa north star: the writer shapes their world by conversation, not by editing JSON.

JQ CANON KEEPER — Docs | HiveJournal