nyxcore-systems
8 min read

Orchestrating Narratives: A Deep Dive into Building nyxBook's Multi-Agent Story Engine

Join me on a journey through the recent sprint to build nyxBook, a multi-agent book orchestration system, covering everything from Prisma schemas to a dynamic UI and the crucial lessons learned along the way.

tRPCPrismaReact QueryNext.jsMulti-AgentAIWorkflow EngineFullstackDeveloper Experience

Building ambitious systems is rarely a straight line. It's a dance between vision, execution, and the inevitable debugging tango. Recently, my focus was entirely on nyxBook – a brand new, full multi-agent book orchestration system designed to live within our nyxCore ecosystem. The goal? To empower writers and storytellers with intelligent agents that can assist in every facet of book creation, from world-building to character development and narrative generation.

This session was all about bringing nyxBook to life. We're talking schema, services, workflow engine extensions, a complete tRPC router, a dashboard UI, sidebar integration, sophisticated persona scoping, and even initial seed data. It was a sprint, to say the least, but I'm thrilled to report: all ten core tasks are complete, typecheck clean, lint clean, and ready for content import.

Let's unpack the journey.

Laying the Foundation: Data Models and Core Services

Any robust application starts with a solid data model. For nyxBook, this meant extending our Prisma schema to capture the essence of a literary work:

  • Book: The central entity.
  • BookChapter: Individual chapters, the backbone of the narrative.
  • BookCharacter: Detailed character profiles, crucial for consistent agent-driven interactions.
  • BookBeat: Story beats, allowing for high-level plot planning and tracking.

Crucially, we also enhanced existing models. Persona.scope was introduced, allowing us to define agents as "global" (available everywhere) or "book" (specific to a particular manuscript). This is fundamental for our multi-agent approach, ensuring that a character's "voice" agent only operates within its designated story. Similarly, Workflow.bookId now links our generative processes directly to a book.

With the data structures in place, the next step was to build the core services that breathe life into nyxBook:

  • nyxbook-import.ts: This service handles the initial ingestion of content. A key design decision here was the "dual-layer parsing" – separating the raw narrative from the Aktenlage (think "case files" or "author's notes"). This allows our agents to understand both the published text and the underlying creative intent.
  • nyxbook-context.ts: Essential for dynamic content, this service resolves template variables like {{book.*}}, pulling relevant book data into prompts and generated text.
  • nyxbook-voice.ts: This is where character voices come alive. It generates CORS-formatted character voice prompts, complete with level-based evolution. Imagine a character's voice prompt becoming more nuanced as their "XP" grows through agent interactions!

Supercharging the Workflow Engine

The nyxCore workflow engine is the heart of our agent orchestration. To support nyxBook, it needed significant enhancements:

  • bookContext in ChainContext: This addition allows our workflow chains to access the full context of the current book, ensuring agents have all the information they need.
  • New Template Variables: We introduced powerful new variables:
    • {{book.worldRules}}: For agents to adhere to the established lore and physics of the story.
    • {{book.characters}}: Providing a dynamic list of all characters and their current states.
    • {{book.beats}}: Giving agents an overview of the plot progression.
    • {{book.character.HANDLE}}: For agents to pull specific details about a particular character by their unique handle.

These extensions transform our generic workflow engine into a specialized storytelling powerhouse, capable of guiding and collaborating with multiple AI personas tailored to the narrative.

Bringing it to Life: tRPC API and a Dynamic UI

A powerful backend needs an intuitive frontend. We built out a comprehensive tRPC router (src/server/trpc/routers/nyxbook.ts) to expose nyxBook's capabilities:

  • Full CRUD operations for books, chapters, characters, and beats.
  • Core Generation Mutations:
    • generateChapter: Kickstarts the chapter writing process.
    • generateAktenlage: Creates the "behind-the-scenes" notes and intentions for a chapter.
    • runVoiceTest: Allows authors to test character voices in isolation.
    • runDialecticalReview: Engages agents in a critical review of content.
  • A pipelineStatus query to track ongoing generative workflows.

To integrate nyxBook seamlessly into nyxCore's dashboard, we added:

  • A dedicated nyxBookRouter registration.
  • A scope filter to our personas.ts router, enabling dynamic display of global vs. book-specific agents.
  • Visual cues on the dashboard for nyxbook process types, using distinct amber icons and colors.
  • A new nyxBook entry in the sidebar, marked with a BookOpen icon under the "Knowledge" group.

The frontend (src/app/(dashboard)/dashboard/nyxbook/page.tsx) features a highly functional, 5-tab layout:

  1. Overview: High-level book stats and project status.
  2. Chapters: A list of all chapters.
  3. Beat Board: A visual grid for plot points, color-coded by status.
  4. Characters: A character bible with detailed profiles.
  5. Workshop: The hub for generative actions.

We followed the InPageSidebar pattern for a consistent UX and developed several key UI components:

  • book-pipeline.tsx: A horizontal, 5-stage visualization of the book creation pipeline with SVG connectors for a slick look.
  • chapter-flow.tsx: Per-chapter step indicators to show progress.
  • beat-board.tsx: The aforementioned grid for plot beats.
  • character-card.tsx: Displays character bible entries, an XP bar (yes, agents can level up!), and a voice test button.

Finally, a dedicated chapter editor (src/app/(dashboard)/dashboard/nyxbook/[bookId]/chapters/[num]/page.tsx) provides a tabbed interface for editing narrative, Aktenlage, and notes, complete with a save mutation.

To get things started, prisma/seed.ts now includes 6 book-scoped literary personas and a nyxBook Literary Team – ready to jump into action!

Lessons from the Trenches: My "Pain Log" as Learning

No development sprint is without its bumps. Here are two critical lessons learned:

1. The Case of the Missing onSuccess in React Query v5

The Challenge: I needed to update local editor state after a useQuery successfully fetched chapter data. My go-to pattern has always been the onSuccess callback.

The Attempt:

typescript
const { data: chapter } = api.nyxbook.chapter.useQuery(
  { bookId, num: chapterNum },
  {
    onSuccess: (data) => {
      // This is where I expected to update my editor state
      setNarrative(data.narrative);
      setAktenlage(data.aktenlage);
    },
  }
);

The Realization:

TS2769: 'onSuccess' does not exist in type 'UseTRPCQueryOptions'

A quick check of React Query v5 documentation (which tRPC v11 now uses) revealed that onSuccess (and onError, onSettled) were removed from useQuery options. They are now part of useMutation or handled differently for queries.

The Solution: The recommended approach for reacting to data changes in useQuery is to use a useEffect hook that watches the data property.

typescript
const { data: chapter } = api.nyxbook.chapter.useQuery({ bookId, num: chapterNum });

useEffect(() => {
  if (chapter) {
    setNarrative(chapter.narrative);
    setAktenlage(chapter.aktenlage);
  }
}, [chapter]); // Dependency array ensures effect runs when chapter data changes

The Lesson: Always keep an eye on major version upgrades of core libraries. Breaking changes, especially in widely used hooks like useQuery, can be subtle but impactful. Reading release notes or quickly consulting documentation can save hours. useEffect is the declarative way to handle side effects when query data loads or changes.

2. The Elusive UI Component

The Challenge: I needed a simple Textarea component for the chapter editor.

The Attempt:

typescript
import { Textarea } from '@/components/ui/textarea'; // Assuming it exists

// ... later in JSX
<Textarea value={narrative} onChange={handleNarrativeChange} />

The Realization:

TS2307: Cannot find module '@/components/ui/textarea'

The module simply didn't exist in our project's component library. A classic case of assuming a component exists where it doesn't.

The Solution: Rather than stopping to build a new component, I opted for a native HTML <textarea> and applied our internal nyx-* CSS utility classes to style it appropriately. This allowed me to keep momentum.

typescript
<textarea
  className="nyx-input nyx-textarea" // Our custom CSS classes
  value={narrative}
  onChange={handleNarrativeChange}
/>

The Lesson: Before importing, verify that the component actually exists in your project's component library. If it doesn't, consider the simplest path forward: use native HTML elements with existing styling, or create a minimal wrapper if design system consistency is paramount and the component is truly missing. Don't let a missing UI component block your feature delivery.

What's Next?

With the core implementation complete, the immediate next steps are focused on validation and testing:

  1. Seed the Database: Run npm run db:seed to populate our book personas and the nyxBook Literary Team.
  2. Test Import: Use the nyxBook UI's "Import" button with our inselWerk source path to bring in initial content.
  3. Verify Chapter Editor: Navigate to a chapter, edit the narrative/Aktenlage, and ensure the save mutation works as expected.
  4. Test Generation: Trigger "Generate Chapter" from the Workshop tab and verify that a workflow is created and progresses.
  5. E2E Persona Scoping: Crucially, confirm that book-specific personas do not appear in regular workflow or discussion selectors, ensuring our scope filtering is robust.

This sprint has been incredibly rewarding, laying the groundwork for a truly intelligent and collaborative storytelling platform. The nyxBook system is poised to redefine how authors interact with their creative process, leveraging the power of multi-agent AI to unlock new narrative possibilities.

Stay tuned for updates as nyxBook continues to evolve!

json
{"thingsDone":["Implemented 4 Prisma models (Book, Chapter, Character, Beat)","Extended Persona and Workflow models for book scoping","Created 3 core backend services (import, context, voice)","Extended workflow engine with bookContext and new template variables","Developed full tRPC router with CRUD and 4 generation mutations","Integrated nyxBook into dashboard UI (sidebar, process indicators, 5-tab page)","Created 4 specific UI components (pipeline, chapter flow, beat board, character card)","Developed chapter editor with narrative/Aktenlage/notes tabs","Added book-scoped personas and literary team seed data"],"pains":[{"issue":"onSuccess removed from React Query v5 useQuery options","workaround":"Replaced with useEffect watching chapter.data","lesson":"Stay updated on library major version changes; use useEffect for data-loaded side effects in queries."},{"issue":"Missing '@/components/ui/textarea' component","workaround":"Used native <textarea> with custom CSS classes","lesson":"Verify component existence before import; use native elements or minimal wrappers to maintain momentum if a component is missing."}],"successes":["All 10 tasks completed","Typecheck clean, lint clean","Ready for import test"],"techStack":["Next.js","React Query v5","tRPC v11","Prisma","PostgreSQL","TypeScript","Tailwind CSS (implied by component structure)","Custom Workflow Engine"]}