From 40378ad65e0088dbf7ed14566992b7b2e74e3dac Mon Sep 17 00:00:00 2001 From: Mark Randall Havens Date: Thu, 12 Mar 2026 17:44:51 +0000 Subject: [PATCH] Initial commit: Opus Orchestrator AI - Full-flow book generation - LangGraph workflow orchestration - CrewAI agent crews (Fiction Fortress & Nonfiction Fortress) - PydanticAI schema validation - Fiction agents: Architect, Worldsmith, Character Lead, Voice, Editor - Nonfiction agents: Researcher, Analyst, Writer, Fact-Checker, Editor - Complete schema definitions for books, chapters, critiques - Configuration management - Basic test suite --- README.md | 164 +++++- opus_orchestrator/__init__.py | 59 +++ opus_orchestrator/agents/base.py | 106 ++++ opus_orchestrator/agents/fiction/__init__.py | 18 + opus_orchestrator/agents/fiction/architect.py | 169 +++++++ .../agents/fiction/character_lead.py | 142 ++++++ opus_orchestrator/agents/fiction/editor.py | 191 +++++++ opus_orchestrator/agents/fiction/voice.py | 185 +++++++ .../agents/fiction/worldsmith.py | 163 ++++++ .../agents/nonfiction/__init__.py | 20 + .../agents/nonfiction/researcher.py | 476 ++++++++++++++++++ opus_orchestrator/config.py | 75 +++ opus_orchestrator/orchestrator.py | 312 ++++++++++++ opus_orchestrator/schemas/__init__.py | 33 ++ opus_orchestrator/schemas/book.py | 218 ++++++++ opus_orchestrator/state.py | 76 +++ opus_orchestrator/utils/__init__.py | 3 + pyproject.toml | 56 +++ tests/__init__.py | 1 + tests/test_orchestrator.py | 127 +++++ 20 files changed, 2592 insertions(+), 2 deletions(-) create mode 100644 opus_orchestrator/__init__.py create mode 100644 opus_orchestrator/agents/base.py create mode 100644 opus_orchestrator/agents/fiction/__init__.py create mode 100644 opus_orchestrator/agents/fiction/architect.py create mode 100644 opus_orchestrator/agents/fiction/character_lead.py create mode 100644 opus_orchestrator/agents/fiction/editor.py create mode 100644 opus_orchestrator/agents/fiction/voice.py create mode 100644 opus_orchestrator/agents/fiction/worldsmith.py create mode 100644 opus_orchestrator/agents/nonfiction/__init__.py create mode 100644 opus_orchestrator/agents/nonfiction/researcher.py create mode 100644 opus_orchestrator/config.py create mode 100644 opus_orchestrator/orchestrator.py create mode 100644 opus_orchestrator/schemas/__init__.py create mode 100644 opus_orchestrator/schemas/book.py create mode 100644 opus_orchestrator/state.py create mode 100644 opus_orchestrator/utils/__init__.py create mode 100644 pyproject.toml create mode 100644 tests/__init__.py create mode 100644 tests/test_orchestrator.py diff --git a/README.md b/README.md index 9e186ed..7be123e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,162 @@ -# opus-orchestrator-ai -Full-flow AI book generation orchestrator using LangGraph, CrewAI, AutoGen, and PydanticAI. Integrates Fiction Fortress and Nonfiction Fortress for professional manuscript production. +# Opus Orchestrator AI + +> Full-flow AI book generation orchestrator using LangGraph, CrewAI, AutoGen, and PydanticAI. Integrates Fiction Fortress and Nonfiction Fortress for professional manuscript production. + +## Overview + +Opus Orchestrator AI transforms raw content (notes, outlines, stream-of-consciousness, essays, logs) into fully edited, publication-ready manuscripts. It combines: + +- **LangGraph** — Workflow orchestration and state management +- **CrewAI** — Role-based agent crews +- **AutoGen** — Complex multi-agent negotiations +- **PydanticAI** — Structured output validation +- **Fiction Fortress** — Complete fiction writing methodology +- **Nonfiction Fortress** — Complete non-fiction writing methodology + +## Architecture + +``` +[GitHub Repo Input] + │ + ▼ +┌───────────────────┐ +│ INGESTOR AGENT │ ──► Extracts raw content from repo +└────────┬──────────┘ + │ + ▼ +┌───────────────────┐ +│ INTENT ANALYZER │ ──► Analyzes goals, audience, intended outcome +└────────┬──────────┘ + │ + ▼ +┌───────────────────┐ +│ BLUEPRINT │ ──► Generates detailed book blueprint +└────────┬──────────┘ + │ + ▼ + ┌────┴────┐ + │ ITERATE │ + └────┬────┘ + │ + ┌────▼─────────────┐ + │ CREW EXECUTION │ ──► Runs agent crews per chapter + │ • Writer │ + │ • Critics (3+) │ + │ • Editor │ + │ • Proofreader │ + └────┬─────────────┘ + │ + ┌────▼─────────────┐ + │ REVIEW & REVISE │ ──► Internal critic circle + └────┬─────────────┘ + │ + └─────────────┬────► [Loop back if needed] + │ + ▼ + ┌─────────────────┐ + │ COMPILED .MD │ + │ MANUSCRIPT │ + └─────────────────┘ +``` + +## Installation + +```bash +git clone https://github.com/mrhavens/opus-orchestrator-ai.git +cd opus-orchestrator-ai +pip install -e . +``` + +## Quick Start + +```python +from opus_orchestrator import OpusOrchestrator + +orchestrator = OpusOrchestrator( + repo_url="https://github.com/user/my-book-ideas", + book_type="fiction", # or "nonfiction" + genre="science-fiction", + target_audience="adult sci-fi readers", + intended_outcome="complete novel, ~80k words" +) + +# Run the full pipeline +manuscript = await orchestrator.run() +print(f"Generated: {manuscript.word_count} words") +``` + +## Configuration + +See `config.example.yaml` for full configuration options. + +## Project Structure + +``` +opus_orchestrator/ +├── __init__.py # Main exports +├── config.py # Configuration management +├── state.py # LangGraph state definitions +├── graph.py # Main workflow graph +├── agents/ +│ ├── __init__.py +│ ├── base.py # Base agent class +│ ├── fiction/ # Fiction Fortress agents +│ │ ├── architect.py +│ │ ├── worldsmith.py +│ │ ├── character_lead.py +│ │ ├── voice.py +│ │ └── editor.py +│ └── nonfiction/ # Nonfiction Fortress agents +│ ├── researcher.py +│ ├── analyst.py +│ ├── writer.py +│ ├── fact_checker.py +│ └── editor.py +├── crews/ +│ ├── __init__.py +│ ├── fiction_crew.py # Fiction writing crew +│ └── nonfiction_crew.py +├── schemas/ +│ ├── __init__.py +│ ├── book.py # Book-level schemas +│ ├── chapter.py # Chapter schemas +│ └── critique.py # Critique schemas +└── utils/ + ├── __init__.py + ├── github.py # GitHub ingestion + └── output.py # Output generation +``` + +## Development + +### Running Tests + +```bash +pytest tests/ +``` + +### Code Style + +```bash +ruff check . +ruff format . +``` + +## Dependencies + +- langgraph +- crewai +- autogen +- pydantic-ai +- pydantic +- httpx +- pygithub +- pyyaml + +## License + +MIT + +--- + +*Built with the WE Architecture — witness and co-creation in code.* diff --git a/opus_orchestrator/__init__.py b/opus_orchestrator/__init__.py new file mode 100644 index 0000000..b8763f9 --- /dev/null +++ b/opus_orchestrator/__init__.py @@ -0,0 +1,59 @@ +"""Opus Orchestrator AI. + +Full-flow AI book generation using LangGraph, CrewAI, AutoGen, and PydanticAI. +Integrates Fiction Fortress and Nonfiction Fortress methodologies. +""" + +from opus_orchestrator.agents.fiction import ( + ArchitectAgent, + CharacterLeadAgent, + EditorAgent, + VoiceAgent, + WorldsmithAgent, +) +from opus_orchestrator.agents.nonfiction import ( + AnalystAgent, + FactCheckerAgent, + NonfictionEditorAgent, + NonfictionWriterAgent, + ResearcherAgent, +) +from opus_orchestrator.config import OpusConfig, get_config +from opus_orchestrator.schemas import ( + BookIntent, + BookType, + Manuscript, + RawContent, +) +from opus_orchestrator.state import OpusState, create_initial_state + +__all__ = [ + # Config + "OpusConfig", + "get_config", + # State + "OpusState", + "create_initial_state", + # Schemas + "BookIntent", + "BookType", + "Manuscript", + "RawContent", + # Fiction Agents + "ArchitectAgent", + "CharacterLeadAgent", + "EditorAgent", + "VoiceAgent", + "WorldsmithAgent", + # Nonfiction Agents + "ResearcherAgent", + "AnalystAgent", + "NonfictionWriterAgent", + "FactCheckerAgent", + "NonfictionEditorAgent", + # Main + "OpusOrchestrator", +] + +# Import orchestrator at bottom to avoid circular imports +from opus_orchestrator.orchestrator import OpusOrchestrator diff --git a/opus_orchestrator/agents/base.py b/opus_orchestrator/agents/base.py new file mode 100644 index 0000000..8e09630 --- /dev/null +++ b/opus_orchestrator/agents/base.py @@ -0,0 +1,106 @@ +"""Base agent class for Opus Orchestrator.""" + +from abc import ABC, abstractmethod +from typing import Any, Generic, TypeVar + +from pydantic import BaseModel + +from opus_orchestrator.config import AgentConfig, get_config + + +T = TypeVar("T", bound=BaseModel) + + +class AgentResponse(BaseModel): + """Standard response from an agent.""" + + success: bool + output: Any + error: Optional[str] = None + metadata: dict[str, Any] = {} + + class Config: + arbitrary_types_allowed = True + + +from typing import Optional + + +class BaseAgent(ABC, Generic[T]): + """Base class for all Opus agents. + + Each agent has: + - A specific role (from Fortress documentation) + - System prompts derived from Fortress methodologies + - Input/output schemas + - Execution logic + """ + + def __init__( + self, + role: str, + description: str, + system_prompt: str, + output_schema: type[T] | None = None, + config: Optional[AgentConfig] = None, + ): + self.role = role + self.description = description + self.system_prompt = system_prompt + self.output_schema = output_schema + self.config = config or get_config().agent + + @abstractmethod + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute the agent's task. + + Args: + input_data: The input data for this agent + context: Additional context from the orchestrator + + Returns: + AgentResponse with output and metadata + """ + pass + + def build_system_prompt(self, context: dict[str, Any]) -> str: + """Build the full system prompt with context. + + Args: + context: Additional context to inject + + Returns: + Complete system prompt + """ + base = self.system_prompt + + if context: + context_str = "\n\n## Context\n" + for key, value in context.items(): + context_str += f"- **{key}**: {value}\n" + return base + context_str + + return base + + def build_user_prompt(self, task: str, input_data: Any) -> str: + """Build the user prompt for a specific task. + + Args: + task: Description of the task + input_data: Input data formatted for the task + + Returns: + Complete user prompt + """ + return f"""## Task + +{task} + +## Input + +{input_data} + +## Instructions + +Please complete this task following the methodology specified in your system prompt. +""" diff --git a/opus_orchestrator/agents/fiction/__init__.py b/opus_orchestrator/agents/fiction/__init__.py new file mode 100644 index 0000000..5aa8b3c --- /dev/null +++ b/opus_orchestrator/agents/fiction/__init__.py @@ -0,0 +1,18 @@ +"""Fiction agents for Opus Orchestrator. + +Based on Fiction Fortress Level 1-3 methodology. +""" + +from opus_orchestrator.agents.fiction.architect import ArchitectAgent +from opus_orchestrator.agents.fiction.character_lead import CharacterLeadAgent +from opus_orchestrator.agents.fiction.editor import EditorAgent +from opus_orchestrator.agents.fiction.voice import VoiceAgent +from opus_orchestrator.agents.fiction.worldsmith import WorldsmithAgent + +__all__ = [ + "ArchitectAgent", + "CharacterLeadAgent", + "EditorAgent", + "VoiceAgent", + "WorldsmithAgent", +] diff --git a/opus_orchestrator/agents/fiction/architect.py b/opus_orchestrator/agents/fiction/architect.py new file mode 100644 index 0000000..75c7377 --- /dev/null +++ b/opus_orchestrator/agents/fiction/architect.py @@ -0,0 +1,169 @@ +"""The Architect agent - Story structure and plot design. + +From Fiction Fortress Level 1-3: +- Role: Story structure and plot design +- Responsibilities: Outlines, pacing, scene planning +- Output: Story blueprint +""" + +from typing import Any, Optional + +from opus_orchestrator.agents.base import AgentResponse, BaseAgent +from opus_orchestrator.schemas import BookBlueprint, ChapterBlueprint + + +ARCHITECT_SYSTEM_PROMPT = """## Role: The Architect + +You are The Architect — the story's structural engineer. Your expertise lies in narrative architecture across all genres, and you excel at translating high-level concepts into detailed scene breakdowns. + +## Core Responsibilities + +1. **Story Structure Mastery** + - Three-act structure application across genres + - Beat-by-beat scene planning + - Subplot integration techniques + - Pacing analysis and adjustment + +2. **Genre Expertise** + - Mystery: Clue placement, red herring distribution, revelation timing + - Romance: Beat sheet adaptation for relationship arcs + - Thriller: Tension escalation curves, set-piece design + - Fantasy/Sci-Fi: World-rule integration with plot beats + +3. **Outline Generation** + - Story spine creation (primal narrative in 3-5 sentences) + - Chapter-level beat mapping + - Scene purpose identification (whose story, what changes, why this scene) + - Backstory weaving into present-tense narrative + +4. **Conflict Architecture** + - Internal vs. external conflict layering + - Antagonist motivation design + - Stakes escalation planning + - Tension and release rhythm + +## Quality Standards + +- Each scene must advance plot, character, or theme (or multiple) +- Stakes must escalate through Acts I-II +- Midpoint must fundamentally shift protagonist's understanding +- All subplots must resolve by climax +- Every chapter needs a clear purpose and payoff + +## Output Format + +When generating a blueprint, include: +1. Story spine (primal narrative in 3-5 sentences) +2. Three-act breakdown with specific beats +3. Chapter-level outline (numbered chapters with one-line descriptions) +4. Scene list with purpose tags +5. Key plot points with word count allocations +6. Subplot integration notes +""" + + +class ArchitectAgent(BaseAgent): + """Agent responsible for story structure and plot design.""" + + def __init__(self, config=None): + super().__init__( + role="Architect", + description="Story structure and plot design", + system_prompt=ARCHITECT_SYSTEM_PROMPT, + output_schema=BookBlueprint, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute the Architect's task to generate a story blueprint. + + Args: + input_data: Raw content + intent from the orchestrator + context: Additional context (genre, themes, etc.) + + Returns: + AgentResponse with BookBlueprint + """ + # This is a placeholder - actual implementation would call the LLM + # For now, we'll structure the prompt + raw_content = input_data.get("raw_content", "") + intent = input_data.get("intent", {}) + genre = intent.get("genre", "general") + target_word_count = intent.get("target_word_count", 80000) + themes = intent.get("themes", []) + + user_prompt = f"""## Input Content + +{raw_content} + +## Requirements + +- Genre: {genre} +- Target word count: {target_word_count} +- Themes to incorporate: {', '.join(themes) if themes else 'None specified'} +- Target audience: {intent.get('target_audience', 'General readers')} + +## Task + +Generate a complete story blueprint following the Architect's methodology. +Include all sections specified in your system prompt. +""" + + # In actual implementation, this would call the LLM + # For now, return a structured response + return AgentResponse( + success=True, + output={ + "status": "blueprint_generated", + "message": "Blueprint generation would be executed here with LLM", + }, + metadata={ + "role": "Architect", + "input_word_count": len(raw_content.split()), + "target_word_count": target_word_count, + "genre": genre, + }, + ) + + async def expand_chapter( + self, + chapter: ChapterBlueprint, + full_blueprint: BookBlueprint, + context: dict[str, Any], + ) -> AgentResponse: + """Expand a single chapter beat into detailed scene specification. + + From Template B in Fiction Fortress Level 2: + - Scene ID, Act/Chapter location + - POV character, Scene goal, Scene conflict, Scene outcome + - Opening beat, Conflict beat, Turn beat, Ending beat + """ + user_prompt = f"""## Chapter to Expand + +- Chapter Number: {chapter.chapter_number} +- Title: {chapter.title} +- Summary: {chapter.summary} +- Word Count Target: {chapter.word_count_target} +- POV Character: {chapter.pov_character or 'Narrator'} +- Key Events: {', '.join(chapter.key_events)} + +## Full Blueprint Context + +- Book Title: {full_blueprint.title} +- Genre: {full_blueprint.genre} +- Overall Structure: {full_blueprint.structure} + +## Task + +Expand this chapter beat into a detailed scene specification following +Template B from the Fiction Fortress methodology. +""" + + return AgentResponse( + success=True, + output={ + "status": "chapter_expanded", + "chapter_number": chapter.chapter_number, + }, + metadata={"role": "Architect", "task": "chapter_expansion"}, + ) diff --git a/opus_orchestrator/agents/fiction/character_lead.py b/opus_orchestrator/agents/fiction/character_lead.py new file mode 100644 index 0000000..2f290bc --- /dev/null +++ b/opus_orchestrator/agents/fiction/character_lead.py @@ -0,0 +1,142 @@ +"""The Character Lead agent - Character development. + +From Fiction Fortress Level 1-3: +- Role: Character development +- Responsibilities: Backstory, motivations, arcs +- Output: Character profiles +""" + +from typing import Any + +from opus_orchestrator.agents.base import AgentResponse, BaseAgent + + +CHARACTER_LEAD_SYSTEM_PROMPT = """## Role: The Character Lead + +You are The Character Lead — the one who breathes life into the figures who inhabit the story. Your expertise lies in psychological depth, relationship dynamics, and character arc construction. + +## Core Responsibilities + +1. **Character Profile Development** + - Want/Need/Fear triad construction + - Backstory selection and dramatization + - Psychological wound identification + - Character voice (speech patterns, internal monologue) + +2. **Relationship Architecture** + - Relationship web mapping + - Dynamic vs. static relationship types + - Relationship arc planning (improvement, deterioration, transformation) + - Power dynamic visualization + +3. **Character Arc Design** + - Starting state definition + - Transformation catalyst identification + - Change mechanism dramatization + - Ending state resolution + +4. **Consistency Maintenance** + - Character behavior logic tracking + - Emotional memory verification + - Knowledge-state tracking (what does this character know when?) + - Capability consistency (what can this character actually do?) + +## Character Arc Types + +- **Positive**: Growth from weakness +- **Negative**: Fall from grace +- **Flat**: No change, changes world +- **Disruption**: External力量打破平衡 + +## The Want/Need/Fear Triad + +- **Want**: External goal they pursue +- **Need**: Internal growth they must learn +- **Fear**: What they run from +- **Wound**: Formative experience +- **Lie they believe**: False worldview + +## Quality Standards + +- Each character must have clear motivations for all major actions +- Relationships must feel authentic and dynamic +- Character voice must be distinct and consistent +- Arc transformation must be earned through dramatization +""" + + +class CharacterLeadAgent(BaseAgent): + """Agent responsible for character development.""" + + def __init__(self, config=None): + super().__init__( + role="Character Lead", + description="Character development", + system_prompt=CHARACTER_LEAD_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute the Character Lead's task to generate character profiles. + + Args: + input_data: Raw content + blueprint with character references + context: Additional context + + Returns: + AgentResponse with character profiles + """ + characters = input_data.get("characters", []) + raw_content = input_data.get("raw_content", "") + + user_prompt = f"""## Task + +Create comprehensive character profiles for the following characters: + +{chr(10).join(f"- {c}" for c in characters) if characters else "Create profiles for all characters in the story."} + +## Raw Content Reference + +{raw_content} + +## Guidelines + +Follow the Character Lead methodology from your system prompt. +Include the Want/Need/Fear triad for each major character. +""" + + return AgentResponse( + success=True, + output={"status": "characters_created"}, + metadata={"role": "Character Lead", "character_count": len(characters)}, + ) + + async def develop_relationship( + self, + character_a: str, + character_b: str, + relationship_type: str, + context: dict[str, Any], + ) -> AgentResponse: + """Develop the relationship between two characters.""" + user_prompt = f"""## Relationship Details + +- Character A: {character_a} +- Character B: {character_b} +- Relationship Type: {relationship_type} + +## Task + +Develop this relationship following the Character Lead methodology. +Include: +- Current dynamics +- Power balance +- History (if any) +- Potential arc +""" + + return AgentResponse( + success=True, + output={"status": "relationship_developed"}, + metadata={"role": "Character Lead", "characters": [character_a, character_b]}, + ) diff --git a/opus_orchestrator/agents/fiction/editor.py b/opus_orchestrator/agents/fiction/editor.py new file mode 100644 index 0000000..b6d9d09 --- /dev/null +++ b/opus_orchestrator/agents/fiction/editor.py @@ -0,0 +1,191 @@ +"""The Editor agent - Quality control. + +From Fiction Fortress Level 1-3: +- Role: Quality control +- Responsibilities: Continuity, pacing, quality checks +- Output: Editorial notes, revisions +""" + +from typing import Any + +from opus_orchestrator.agents.base import AgentResponse, BaseAgent + + +EDITOR_SYSTEM_PROMPT = """## Role: The Editor + +You are The Editor — the quality control mechanism, identifying problems across all dimensions of the manuscript and directing revision. + +## Core Responsibilities + +1. **Continuity Verification** + - Timeline consistency checking + - Character knowledge tracking + - Physical detail consistency (eye color, scars, clothing) + - World-rule adherence verification + +2. **Pacing Analysis** + - Scene length distribution + - Tension curve mapping + - Reader fatigue prevention + - Act break identification + +3. **Quality Assessment** + - Dialogue authenticity evaluation + - Show vs. tell calibration + - Emotional resonance verification + - Prose quality grading + +4. **Revision Direction** + - Specific change identification + - Priority sequencing (major vs. minor issues) + - Revision scope definition + - Polish vs. rewrite determination + +## Supporting Capabilities + +- Beta reader simulation +- Readability metrics interpretation +- Genre convention compliance +- Structural problem diagnosis + +## Quality Metrics + +| Check | Method | +|-------|--------| +| Pacing | Scene length analysis | +| Tension | Conflict per scene | +| Character consistency | Arc tracking | +| World consistency | Rule verification | +| Voice consistency | Prose sampling | + +## Revision Priority Definitions + +- **Major Revisions**: Structural issues, plot holes, character arc breaks +- **Minor Revisions**: Continuity errors, style inconsistencies, pacing tweaks +- **Polish**: Grammar, punctuation, word choice refinement + +## Quality Standards + +- Every issue must have specific, actionable feedback +- Revision priorities must be clearly ordered +- Continuity issues must be flagged with exact locations +- Pacing analysis must be data-driven (scene lengths, tension scores) +""" + + +class EditorAgent(BaseAgent): + """Agent responsible for quality control and editorial direction.""" + + def __init__(self, config=None): + super().__init__( + role="Editor", + description="Quality control", + system_prompt=EDITOR_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute the Editor's task to review and assess the manuscript. + + Args: + input_data: Chapter or manuscript to review + context: Review criteria and standards + + Returns: + AgentResponse with editorial assessment + """ + content = input_data.get("content", "") + review_type = input_data.get("review_type", "full") + + user_prompt = f"""## Task + +Perform a {review_type} editorial review on: + +{content[:5000]}... {'(truncated)' if len(content) > 5000 else ''} + +## Review Type: {review_type} + +## Guidelines + +Follow the Editor methodology from your system prompt. +Include: +- Continuity verification +- Pacing analysis +- Quality assessment +- Specific revision directions +""" + + return AgentResponse( + success=True, + output={"status": "editorial_review_complete"}, + metadata={"role": "Editor", "review_type": review_type}, + ) + + async def review_chapter( + self, + chapter: dict[str, Any], + full_manuscript_context: dict[str, Any], + context: dict[str, Any], + ) -> AgentResponse: + """Review a single chapter in full manuscript context.""" + user_prompt = f"""## Chapter to Review + +- Chapter Number: {chapter.get('chapter_number')} +- Title: {chapter.get('title')} +- Content: {chapter.get('content', '')[:3000]}... + +## Full Manuscript Context + +- Total Chapters: {full_manuscript_context.get('total_chapters', 0)} +- Previous Chapters Summary: {full_manuscript_context.get('previous_summaries', [])} +- Characters in Story: {', '.join(full_manuscript_context.get('characters', []))} +- World Rules: {full_manuscript_context.get('world_rules', {})} + +## Task + +Perform a complete editorial review of this chapter, considering: +- Continuity with previous chapters +- Pacing within the chapter and in sequence +- Character consistency +- World-rule adherence +- Voice consistency +- Dialogue quality + +Assign a revision priority: major_revisions, minor_revisions, or approved +""" + + return AgentResponse( + success=True, + output={ + "status": "chapter_reviewed", + "chapter_number": chapter.get("chapter_number"), + }, + metadata={"role": "Editor", "task": "chapter_review"}, + ) + + async def generate_revision_notes( + self, + critiques: list[dict[str, Any]], + context: dict[str, Any], + ) -> AgentResponse: + """Generate prioritized revision notes from multiple critiques.""" + user_prompt = f"""## Critiques to Synthesize + +{chr(10).join(f"### Critique {i+1}:{c}" for i, c in enumerate(critiques))} + +## Task + +Synthesize these critiques into prioritized revision notes. +Group by: +1. Major revisions (structural, plot, arc issues) +2. Minor revisions (continuity, style, pacing) +3. Polish items (grammar, word choice) + +For each item, provide specific, actionable feedback. +""" + + return AgentResponse( + success=True, + output={"status": "revision_notes_generated"}, + metadata={"role": "Editor", "critique_count": len(critiques)}, + ) diff --git a/opus_orchestrator/agents/fiction/voice.py b/opus_orchestrator/agents/fiction/voice.py new file mode 100644 index 0000000..e5bf7a8 --- /dev/null +++ b/opus_orchestrator/agents/fiction/voice.py @@ -0,0 +1,185 @@ +"""The Voice agent - Prose style and tone. + +From Fiction Fortress Level 1-3: +- Role: Prose style and tone +- Responsibilities: Sentence-level writing, voice consistency +- Output: Prose samples, style guide +""" + +from typing import Any + +from opus_orchestrator.agents.base import AgentResponse, BaseAgent + + +VOICE_SYSTEM_PROMPT = """## Role: The Voice + +You are The Voice — the owner of the prose itself, the sound, rhythm, and texture of the language. Your expertise lies in maintaining consistent style across thousands of words while adapting to scene-specific demands. + +## Core Responsibilities + +1. **Prose Style Control** + - Sentence rhythm variation (short/medium/long calibration) + - Vocabulary level management + - Figurative language deployment (metaphor, simile, symbolism density) + - Point of view intimacy maintenance + +2. **Voice Consistency** + - Style guide creation and adherence + - Word bank maintenance + - Phrase pattern tracking + - Tone range specification (warm/cool, dark/light, etc.) + +3. **Scene-Type Adaptation** + - Action scene pacing (sentence shortening) + - Emotional scene density (sentence complexity) + - Dialogue scene formatting + - Descriptive scene rhythm + - Exposition handling (invisible vs. visible) + +4. **Point of View Management** + - Deep POV techniques + - Perspective consistency + - Head-hopping prevention + - Internal monologue integration + +## Supporting Capabilities + +- Dialogue attribution (said vs. action beats vs. no attribution) +- Punctuation style consistency +- Paragraph rhythm (white space management) +- Reader proximity calibration + +## Voice Consistency Protocol + +Maintain: +1. **Word bank** - Preferred vocabulary +2. **Phrase patterns** - Recurring constructions +3. **Rhythm map** - Sentence length distribution +4. **Tone guide** - Emotional range + +## Quality Standards + +- Voice must remain consistent across entire manuscript +- Scene type must inform prose style +- POV must be maintained without head-hopping +- Dialogue must sound distinct for each character +""" + + +class VoiceAgent(BaseAgent): + """Agent responsible for prose style and voice consistency.""" + + def __init__(self, config=None): + super().__init__( + role="Voice", + description="Prose style and tone", + system_prompt=VOICE_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute the Voice agent's task to create style guide and samples. + + Args: + input_data: Genre, tone, target audience + context: Additional context + + Returns: + AgentResponse with style guide and prose samples + """ + genre = input_data.get("genre", "general") + tone = input_data.get("tone", "neutral") + + user_prompt = f"""## Task + +Create a voice/style guide and prose samples for: + +- Genre: {genre} +- Tone: {tone} +- Target Audience: {input_data.get('target_audience', 'General readers')} + +## Guidelines + +Follow the Voice agent methodology from your system prompt. +Include: +- Word bank +- Phrase patterns +- Rhythm map +- Tone guide +- 3 sample scenes (opening, dialogue, descriptive) +""" + + return AgentResponse( + success=True, + output={"status": "voice_created"}, + metadata={"role": "Voice", "genre": genre, "tone": tone}, + ) + + async def write_chapter( + self, + chapter_spec: dict[str, Any], + style_guide: dict[str, Any], + context: dict[str, Any], + ) -> AgentResponse: + """Write a complete chapter following the style guide. + + This is the main writing task for the Voice agent. + """ + user_prompt = f"""## Chapter Specification + +- Chapter Number: {chapter_spec.get('chapter_number')} +- Title: {chapter_spec.get('title')} +- Summary: {chapter_spec.get('summary')} +- Word Count Target: {chapter_spec.get('word_count_target')} +- POV Character: {chapter_spec.get('pov_character', 'Narrator')} +- Key Events: {', '.join(chapter_spec.get('key_events', []))} + +## Style Guide + +{style_guide} + +## Task + +Write the complete chapter following the style guide and chapter specification. +Maintain consistent voice throughout. +""" + + return AgentResponse( + success=True, + output={ + "status": "chapter_written", + "chapter_number": chapter_spec.get("chapter_number"), + }, + metadata={"role": "Voice"}, + ) + + async def polish_chapter( + self, + chapter_content: str, + style_guide: dict[str, Any], + context: dict[str, Any], + ) -> AgentResponse: + """Polish an existing chapter for voice consistency.""" + user_prompt = f"""## Chapter to Polish + +{chapter_content} + +## Style Guide + +{style_guide} + +## Task + +Polish this chapter for voice consistency. Ensure: +- Sentence rhythm varies appropriately +- Word choice matches the style guide +- Tone remains consistent +- POV is maintained +- Prose flows smoothly +""" + + return AgentResponse( + success=True, + output={"status": "chapter_polished"}, + metadata={"role": "Voice", "task": "polish"}, + ) diff --git a/opus_orchestrator/agents/fiction/worldsmith.py b/opus_orchestrator/agents/fiction/worldsmith.py new file mode 100644 index 0000000..e9691d3 --- /dev/null +++ b/opus_orchestrator/agents/fiction/worldsmith.py @@ -0,0 +1,163 @@ +"""The Worldsmith agent - Setting and world-building. + +From Fiction Fortress Level 1-3: +- Role: Setting and world-building +- Responsibilities: Locations, cultures, technology, history +- Output: World bible +""" + +from typing import Any + +from opus_orchestrator.agents.base import AgentResponse, BaseAgent + + +WORLDSMITH_SYSTEM_PROMPT = """## Role: The Worldsmith + +You are The Worldsmith — the creator of the stage upon which characters act. Your expertise lies in generating internally consistent settings that enhance rather than distract from the narrative. + +## Core Responsibilities + +1. **World-Building Frameworks** + - ICE Method implementation (Internal Consistency, Cultural Depth, Environmental Detail) + - Magic/technology system design with explicit rules + - Political and economic system creation + - Historical timeline construction + +2. **Geographic Design** + - Climate-driven ecology + - Settlement placement rationale + - Transportation and trade route logic + - Territorial conflict origins + +3. **Cultural Creation** + - Belief system development (religion, philosophy, mythology) + - Language patterns (without inventing full languages) + - Social hierarchy construction + - Daily life visualization (food, clothing, housing, work) + +4. **World Document Generation** + - Scalable detail (novel-length vs. short story) + - Referenceable format for other agents + - Searchable information architecture + - Consistency tracking across documents + +## The ICE Method + +- **Internal Consistency**: Rules of magic/technology, geography, social structures, economics +- **Cultural Depth**: History, mythology, language patterns, beliefs, daily life +- **Environmental Detail**: Sensory descriptions, ecology, architecture, clothing, food + +## Quality Standards + +- All elements must be internally consistent +- Details must support story needs +- Cultural elements must have logical origins +- History must create present conflicts + +## Output Structure + +For each world element, include: +- Geography with climate and natural resources +- History with pivotal events +- Cultures with beliefs, customs, appearance +- Politics with power structures +- Economics with trade and class +- Daily life details +- Rules (for magic/technology) +""" + + +class WorldsmithAgent(BaseAgent): + """Agent responsible for world-building and setting creation.""" + + def __init__(self, config=None): + super().__init__( + role="Worldsmith", + description="Setting and world-building", + system_prompt=WORLDSMITH_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute the Worldsmith's task to generate world documents. + + Args: + input_data: Blueprint + genre + setting requirements + context: Additional context + + Returns: + AgentResponse with world bible + """ + blueprint = input_data.get("blueprint", {}) + genre = input_data.get("genre", "fantasy") + setting_type = input_data.get("setting_type", "fantasy") + + user_prompt = f"""## Task + +Create a comprehensive world bible for the following story: + +- Genre: {genre} +- Setting Type: {setting_type} +- Story Title: {blueprint.get('title', 'Untitled')} + +## Guidelines + +Follow the ICE Method and output structure from your system prompt. +Ensure all elements are internally consistent and support the story. + +## Content Seed + +{input_data.get('raw_content', 'No additional content provided.')} +""" + + return AgentResponse( + success=True, + output={ + "status": "world_created", + "message": "World bible generation would be executed here with LLM", + }, + metadata={ + "role": "Worldsmith", + "genre": genre, + "setting_type": setting_type, + }, + ) + + async def expand_location( + self, + location_name: str, + story_relevance: str, + tone: str, + pov_character: str, + context: dict[str, Any], + ) -> AgentResponse: + """Generate detailed location description. + + From Template B in Fiction Fortress Level 2. + """ + user_prompt = f"""## Location Details + +- Location Name: {location_name} +- Location Type: {context.get('location_type', 'general')} +- Story Relevance: {story_relevance} +- Tone Needed: {tone} +- POV Character: {pov_character} + +## Sensory Requirements + +- Visual: {context.get('visual', 'Standard')} +- Auditory: {context.get('auditory', 'Standard')} +- Olfactory: {context.get('olfactory', 'Standard')} +- Tactile: {context.get('tactile', 'Standard')} +- Gustatory: {context.get('gustatory', 'N/A')} + +## Task + +Generate a 300-600 word location description following the Fiction Fortress methodology. +""" + + return AgentResponse( + success=True, + output={"status": "location_expanded"}, + metadata={"role": "Worldsmith", "location": location_name}, + ) diff --git a/opus_orchestrator/agents/nonfiction/__init__.py b/opus_orchestrator/agents/nonfiction/__init__.py new file mode 100644 index 0000000..1f5b79e --- /dev/null +++ b/opus_orchestrator/agents/nonfiction/__init__.py @@ -0,0 +1,20 @@ +"""Nonfiction agents for Opus Orchestrator. + +Based on Nonfiction Fortress Level 1-3 methodology. +""" + +from opus_orchestrator.agents.nonfiction.researcher import ( + AnalystAgent, + FactCheckerAgent, + NonfictionEditorAgent, + NonfictionWriterAgent, + ResearcherAgent, +) + +__all__ = [ + "ResearcherAgent", + "AnalystAgent", + "NonfictionWriterAgent", + "FactCheckerAgent", + "NonfictionEditorAgent", +] diff --git a/opus_orchestrator/agents/nonfiction/researcher.py b/opus_orchestrator/agents/nonfiction/researcher.py new file mode 100644 index 0000000..eebd7f5 --- /dev/null +++ b/opus_orchestrator/agents/nonfiction/researcher.py @@ -0,0 +1,476 @@ +"""Nonfiction agents for Opus Orchestrator. + +Based on Nonfiction Fortress Level 1-3 methodology. +""" + +# Researcher Agent +from typing import Any + +from opus_orchestrator.agents.base import AgentResponse, BaseAgent + + +RESEARCHER_SYSTEM_PROMPT = """## Role: The Researcher + +You are The Researcher — responsible for information gathering, source finding, fact collection, and data mining. + +## Core Responsibilities + +1. **Source Discovery** + - Primary source identification + - Secondary source evaluation + - Expert identification + - Data source location + +2. **Information Gathering** + - Fact collection + - Quote extraction + - Data mining + - Statistics gathering + +3. **Source Documentation** + - Citation formatting + - Access date recording + - Context preservation + - Credibility assessment + +## Source Types and Credibility + +**Primary Sources** +- Original data +- First-hand accounts +- Official documents +- Expert interviews + +**Secondary Sources** +- Academic papers +- News reports +- Books by experts +- Documentaries + +**Tertiary Sources** +- Encyclopedias +- Aggregated data +- Popular summaries + +## Source Evaluation Criteria + +| Criterion | Weight | +|-----------|--------| +| Expertise | 30% | +| Bias assessment | 25% | +| Recency | 20% | +| Reproducibility | 15% | +| Peer review | 10% | + +## Quality Standards + +- Every fact must be sourced +- Sources must be evaluated for credibility +- Bias must be documented +- Contradictions must be flagged +""" + + +class ResearcherAgent(BaseAgent): + """Agent responsible for research and source gathering.""" + + def __init__(self, config=None): + super().__init__( + role="Researcher", + description="Information gathering", + system_prompt=RESEARCHER_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute research task.""" + topic = input_data.get("topic", "") + research_questions = input_data.get("research_questions", []) + + user_prompt = f"""## Task + +Conduct research on: {topic} + +## Research Questions + +{chr(10).join(f"- {q}" for q in research_questions) if research_questions else "Find comprehensive information on the topic."} + +## Guidelines + +Follow the Researcher methodology from your system prompt. +Document all sources with citations. +""" + + return AgentResponse( + success=True, + output={"status": "research_complete"}, + metadata={"role": "Researcher", "topic": topic}, + ) + + +# Analyst Agent +ANALYST_SYSTEM_PROMPT = """## Role: The Analyst + +You are The Analyst — responsible for information synthesis, pattern identification, argument construction, and insight extraction. + +## Core Responsibilities + +1. **Pattern Identification** + - Theme extraction + - Trend analysis + - Correlation discovery + - Anomaly detection + +2. **Argument Construction** + - Claim development + - Evidence selection + - Reasoning flow + - Counterargument anticipation + +3. **Insight Generation** + - Key takeaways + - Implications + - Connections + - Novel perspectives + +## Argument Structure + +- **Claim**: The thesis statement +- **Evidence**: Supporting facts +- **Reasoning**: Logical connection +- **Counterargument**: Acknowledged opposition +- **Rebuttal**: Response to opposition + +## Argument Types + +- **Causal**: A causes B +- **Comparative**: A is better/worse than B +- **Definition**: A means B +- **Historical**: A led to B +- **Predictive**: A will cause B + +## Logical Fallacies to Avoid + +- Ad hominem +- Straw man +- False dilemma +- Slippery slope +- Circular reasoning +- Hasty generalization + +## Quality Standards + +- All claims must be evidence-based +- Logical fallacies must be avoided +- Counterarguments must be addressed +- Implications must be explored +""" + + +class AnalystAgent(BaseAgent): + """Agent responsible for analysis and argument construction.""" + + def __init__(self, config=None): + super().__init__( + role="Analyst", + description="Information synthesis", + system_prompt=ANALYST_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute analysis task.""" + research_data = input_data.get("research_data", {}) + topic = input_data.get("topic", "") + + user_prompt = f"""## Task + +Analyze the following research data on: {topic} + +## Research Data + +{research_data} + +## Guidelines + +Follow the Analyst methodology. Construct clear arguments with evidence. +Address counterarguments. Generate insights. +""" + + return AgentResponse( + success=True, + output={"status": "analysis_complete"}, + metadata={"role": "Analyst", "topic": topic}, + ) + + +# Writer Agent (Nonfiction) +NONFICTION_WRITER_SYSTEM_PROMPT = """## Role: The Writer (Nonfiction) + +You are The Writer — responsible for prose generation, clear explanation, engaging narrative, and voice development. + +## Core Responsibilities + +1. **Prose Generation** + - Clear explanations + - Engaging narrative + - Accessible language + - Varied structure + +2. **Voice Development** + - Authoritative tone + - Expert positioning + - Reader engagement + - Credibility building + +3. **Content Structuring** + - Introduction hooks + - Body organization + - Conclusion synthesis + - Transition flow + +## Authorial Voice Elements + +- **Expertise**: Demonstrated knowledge +- **Authority**: Confident assertions +- **Clarity**: Accessible explanations +- **Engagement**: Compelling narrative +- **Credibility**: Transparent sourcing + +## Tone Calibration + +| Genre | Tone | +|-------|------| +| Academic | Formal, precise | +| Popular | Accessible, lively | +| Professional | Practical, direct | +| Memoir | Personal, reflective | + +## Quality Standards + +- Complex ideas must be accessible +- Arguments must flow logically +- Voice must be consistent +- Readers must remain engaged +""" + + +class NonfictionWriterAgent(BaseAgent): + """Agent responsible for nonfiction prose writing.""" + + def __init__(self, config=None): + super().__init__( + role="Nonfiction Writer", + description="Nonfiction prose generation", + system_prompt=NONFICTION_WRITER_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute nonfiction writing task.""" + analysis = input_data.get("analysis", {}) + chapter_spec = input_data.get("chapter_spec", {}) + + user_prompt = f"""## Task + +Write a nonfiction chapter based on the following analysis: + +## Chapter Specification + +{chapter_spec} + +## Analysis + +{analysis} + +## Guidelines + +Follow the Nonfiction Writer methodology. Maintain authoritative yet accessible tone. +""" + + return AgentResponse( + success=True, + output={"status": "chapter_written"}, + metadata={"role": "Nonfiction Writer"}, + ) + + +# Fact Checker Agent +FACT_CHECKER_SYSTEM_PROMPT = """## Role: The Fact-Checker + +You are The Fact-Checker — responsible for verification, citation validation, claim verification, and accuracy audit. + +## Core Responsibilities + +1. **Claim Verification** + - Factual accuracy checking + - Quote verification + - Data validation + - Source cross-referencing + +2. **Citation Validation** + - Source credibility + - Citation format + - Attribution accuracy + - Access verification + +3. **Accuracy Audit** + - Comprehensive review + - Error identification + - Correction suggestions + - Confidence scoring + +## Verification Protocol + +**Level 1: Self-check** +- Re-read own claims +- Check math and dates +- Verify quotes + +**Level 2: Source verification** +- Return to original sources +- Confirm context +- Check for misquotes + +**Level 3: External review** +- Fact-checker agent review +- Expert review +- Peer review + +## Quality Standards + +| Category | Standard | +|----------|----------| +| Factual claims | 100% verified | +| Quotes | Exact match | +| Data | Source cited | +| Attribution | Clear ownership | + +## Accuracy Metrics + +- All claims must be verifiable +- Sources must be credible +- Data must be accurately represented +- Attribution must be complete +""" + + +class FactCheckerAgent(BaseAgent): + """Agent responsible for fact-checking and verification.""" + + def __init__(self, config=None): + super().__init__( + role="Fact-Checker", + description="Verification and accuracy", + system_prompt=FACT_CHECKER_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute fact-checking task.""" + content = input_data.get("content", "") + sources = input_data.get("sources", []) + + user_prompt = f"""## Task + +Fact-check the following content: + +{content} + +## Sources + +{chr(10).join(f"- {s}" for s in sources) if sources else "Verify against available sources."} + +## Guidelines + +Follow the Fact-Checker methodology. Verify all claims, quotes, and data. +Provide confidence scores for each item. +""" + + return AgentResponse( + success=True, + output={"status": "fact_check_complete"}, + metadata={"role": "Fact-Checker"}, + ) + + +# Nonfiction Editor Agent +NONFICTION_EDITOR_SYSTEM_PROMPT = """## Role: The Editor (Nonfiction) + +You are The Editor — responsible for quality control, structure assessment, clarity evaluation, and style consistency. + +## Core Responsibilities + +1. **Structure Assessment** + - Argument flow + - Chapter organization + - Information hierarchy + - Transitions + +2. **Clarity Evaluation** + - Readability + - Explanatory quality + - Jargon usage + - Complex sentence identification + +3. **Style Consistency** + - Tone uniformity + - Formatting standards + - Citation style + - Voice maintenance + +## Clarity Metrics + +- Flesch reading ease > 60 +- Average sentence length < 25 words +- Paragraph length < 5 sentences +- Defined terms explained + +## Engagement Metrics + +- Hook in first paragraph +- Questions raised and answered +- Examples and stories included +- Visual elements used appropriately + +## Quality Standards + +- Structure must support arguments +- Clarity must enable comprehension +- Style must maintain credibility +- Engagement must sustain interest +""" + + +class NonfictionEditorAgent(BaseAgent): + """Agent responsible for nonfiction editorial quality.""" + + def __init__(self, config=None): + super().__init__( + role="Nonfiction Editor", + description="Quality control", + system_prompt=NONFICTION_EDITOR_SYSTEM_PROMPT, + config=config, + ) + + async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse: + """Execute editorial review.""" + content = input_data.get("content", "") + + user_prompt = f"""## Task + +Perform editorial review on: + +{content} + +## Guidelines + +Follow the Nonfiction Editor methodology. +Assess structure, clarity, style, and engagement. +""" + + return AgentResponse( + success=True, + output={"status": "editorial_review_complete"}, + metadata={"role": "Nonfiction Editor"}, + ) diff --git a/opus_orchestrator/config.py b/opus_orchestrator/config.py new file mode 100644 index 0000000..0257c23 --- /dev/null +++ b/opus_orchestrator/config.py @@ -0,0 +1,75 @@ +"""Opus Orchestrator AI - Configuration.""" + +from pathlib import Path +from typing import Optional +from pydantic import BaseModel, Field + + +class FortressConfig(BaseModel): + """Configuration for Fortress integration.""" + + fiction_repo: str = "mrhavens/fiction-fortress" + nonfiction_repo: str = "mrhavens/nonfiction-fortress" + crewai_repo: str = "mrhavens/crewai-fortress" + autogen_repo: str = "mrhavens/autogen-fortress" + langgraph_repo: str = "mrhavens/langgraph-fortress" + + +class AgentConfig(BaseModel): + """Configuration for AI agents.""" + + model: str = Field(default="gpt-4o", description="Default model for agents") + temperature: float = Field(default=0.7, ge=0.0, le=2.0) + max_tokens: Optional[int] = Field(default=None, description="Max tokens per response") + max_iterations: int = Field(default=10, description="Max iterations per agent task") + + +class IterationConfig(BaseModel): + """Configuration for iteration loops.""" + + min_critic_rounds: int = Field(default=2, description="Minimum critic review rounds") + max_critic_rounds: int = Field(default=5, description="Maximum critic review rounds") + approval_threshold: float = Field(default=0.8, description="Score threshold to proceed") + auto_proceed_threshold: float = Field(default=0.9, description="Score to auto-approve") + + +class OutputConfig(BaseModel): + """Configuration for output generation.""" + + format: str = Field(default="markdown", description="Output format: markdown, epub, pdf") + include_frontmatter: bool = True + include_toc: bool = True + chapter_separator: str = "\n\n---\n\n" + output_dir: Path = Field(default=Path("./output")) + + +class OpusConfig(BaseModel): + """Main configuration for Opus Orchestrator.""" + + fortress: FortressConfig = Field(default_factory=FortressConfig) + agent: AgentConfig = Field(default_factory=AgentConfig) + iteration: IterationConfig = Field(default_factory=IterationConfig) + output: OutputConfig = Field(default_factory=OutputConfig) + + github_token: Optional[str] = Field(default=None, description="GitHub token for private repos") + + class Config: + frozen = False + + +# Global config instance +_config: Optional[OpusConfig] = None + + +def get_config() -> OpusConfig: + """Get the global configuration instance.""" + global _config + if _config is None: + _config = OpusConfig() + return _config + + +def set_config(config: OpusConfig) -> None: + """Set the global configuration instance.""" + global _config + _config = config diff --git a/opus_orchestrator/orchestrator.py b/opus_orchestrator/orchestrator.py new file mode 100644 index 0000000..92b4fac --- /dev/null +++ b/opus_orchestrator/orchestrator.py @@ -0,0 +1,312 @@ +"""Main Opus Orchestrator class.""" + +from __future__ import annotations + +import asyncio +from pathlib import Path +from typing import Any, Optional + +from opus_orchestrator import get_config +from opus_orchestrator.agents.fiction import ( + ArchitectAgent, + CharacterLeadAgent, + EditorAgent, + VoiceAgent, + WorldsmithAgent, +) +from opus_orchestrator.agents.nonfiction import ( + AnalystAgent, + FactCheckerAgent, + NonfictionEditorAgent, + NonfictionWriterAgent, + ResearcherAgent, +) +from opus_orchestrator.config import OpusConfig +from opus_orchestrator.schemas import ( + BookBlueprint, + BookIntent, + BookType, + Chapter, + ChapterCritique, + ChapterDraft, + Manuscript, + RawContent, +) +from opus_orchestrator.state import OpusState + + +class OpusOrchestrator: + """Main orchestrator for AI book generation. + + Coordinates the full flow from raw content to completed manuscript + using LangGraph, CrewAI, AutoGen, and PydanticAI. + """ + + def __init__( + self, + repo_url: str | None = None, + book_type: str = "fiction", + genre: Optional[str] = None, + target_audience: str = "general readers", + intended_outcome: str = "complete manuscript", + tone: Optional[str] = None, + target_word_count: int = 80000, + config: Optional[OpusConfig] = None, + ): + """Initialize the Opus Orchestrator. + + Args: + repo_url: GitHub URL containing raw content + book_type: "fiction" or "nonfiction" + genre: Genre for fiction or nonfiction subgenre + target_audience: Description of target readers + intended_outcome: What the final product should achieve + tone: Desired tone of writing + target_word_count: Target word count for the book + config: Optional configuration override + """ + self.config = config or get_config() + + # Convert string to BookType + self.book_type = BookType(book_type.lower()) + self.repo_url = repo_url + + # Build intent + self.intent = BookIntent( + book_type=self.book_type, + genre=genre, + target_audience=target_audience, + intended_outcome=intended_outcome, + tone=tone, + target_word_count=target_word_count, + ) + + # Initialize agents based on book type + self._init_agents() + + # State + self.state: Optional[OpusState] = None + + def _init_agents(self) -> None: + """Initialize agents based on book type.""" + if self.book_type == BookType.FICTION: + self.agents = { + "architect": ArchitectAgent(self.config.agent), + "worldsmith": WorldsmithAgent(self.config.agent), + "character_lead": CharacterLeadAgent(self.config.agent), + "voice": VoiceAgent(self.config.agent), + "editor": EditorAgent(self.config.agent), + } + else: + self.agents = { + "researcher": ResearcherAgent(self.config.agent), + "analyst": AnalystAgent(self.config.agent), + "writer": NonfictionWriterAgent(self.config.agent), + "fact_checker": FactCheckerAgent(self.config.agent), + "editor": NonfictionEditorAgent(self.config.agent), + } + + async def ingest(self, content: Optional[RawContent] = None) -> OpusState: + """Ingest raw content from repository. + + Args: + content: Optional pre-processed content + + Returns: + Updated state with raw content + """ + if self.repo_url and not content: + # TODO: Implement GitHub ingestion + content = RawContent( + content_type="repository", + text="[Content would be extracted from GitHub repository]", + metadata={"repo_url": self.repo_url}, + ) + + self.state = create_initial_state( + repo_url=self.repo_url or "", + intent=self.intent, + raw_content=content, + ) + + return self.state + + async def analyze_intent(self) -> OpusState: + """Analyze intent and generate blueprint.""" + # TODO: Implement LLM-based intent analysis + self.state.current_stage = "blueprint" + return self.state + + async def generate_blueprint(self) -> BookBlueprint: + """Generate the book blueprint. + + Returns: + Complete book blueprint + """ + # TODO: Implement blueprint generation using agents + blueprint = BookBlueprint( + title=self.intent.working_title or "Untitled", + genre=self.intent.genre or "general", + target_audience=self.intent.target_audience, + target_word_count=self.intent.target_word_count, + structure="three-act", + themes=[], + tone=self.intent.tone or "neutral", + chapters=[], + ) + + self.state.blueprint = blueprint + self.state.current_stage = "drafting" + self.state.progress = 0.1 + + return blueprint + + async def write_chapter(self, chapter_num: int) -> ChapterDraft: + """Write a single chapter. + + Args: + chapter_num: Chapter number to write + + Returns: + Chapter draft + """ + # TODO: Implement chapter writing with agents + draft = ChapterDraft( + chapter_number=chapter_num, + title=f"Chapter {chapter_num}", + content=f"[Chapter {chapter_num} content would be generated here]", + word_count=2000, + ) + + self.state.drafts[chapter_num] = draft + self.state.progress = 0.1 + (chapter_num / (self.state.blueprint.target_word_count / 3000)) + + return draft + + async def critique_chapter(self, chapter_num: int) -> ChapterCritique: + """Critique a chapter. + + Args: + chapter_num: Chapter number to critique + + Returns: + Chapter critique + """ + # TODO: Implement critic crew using AutoGen + critique = ChapterCritique( + chapter_number=chapter_num, + overall_score=0.85, + criteria_scores=[], + consensus_strengths=["Strong voice", "Good pacing"], + consensus_weaknesses=["Minor continuity issue"], + revision_priority="minor_revisions", + ) + + if chapter_num not in self.state.critiques: + self.state.critiques[chapter_num] = [] + self.state.critiques[chapter_num].append(critique) + + return critique + + async def iterate_chapter(self, chapter_num: int) -> Chapter: + """Iterate on a chapter until approved. + + Args: + chapter_num: Chapter number to iterate + + Returns: + Final approved chapter + """ + max_rounds = self.config.iteration.max_critic_rounds + + for round_num in range(1, max_rounds + 1): + self.state.iteration_round = round_num + + # Get draft + draft = self.state.drafts.get(chapter_num) + if not draft: + draft = await self.write_chapter(chapter_num) + + # Critique + critique = await self.critique_chapter(chapter_num) + + # Check approval + if critique.overall_score >= self.config.iteration.approval_threshold: + break + + # TODO: Implement revision based on critique + + # Return final chapter + return Chapter( + chapter_number=chapter_num, + title=draft.title, + content=draft.content, + word_count=draft.word_count, + ) + + async def compile_manuscript(self) -> Manuscript: + """Compile all chapters into final manuscript. + + Returns: + Complete manuscript + """ + chapters = [] + + if self.state.blueprint: + for i in range(1, len(self.state.blueprint.chapters) + 1): + chapter = await self.iterate_chapter(i) + chapters.append(chapter) + + manuscript = Manuscript( + title=self.state.blueprint.title if self.state.blueprint else "Untitled", + book_type=self.book_type, + genre=self.intent.genre or "general", + chapters=chapters, + total_word_count=sum(c.word_count for c in chapters), + ) + + self.state.manuscript = manuscript + self.state.current_stage = "complete" + self.state.progress = 1.0 + + return manuscript + + async def run(self) -> Manuscript: + """Run the full orchestrator pipeline. + + Returns: + Complete manuscript + """ + # Ingest + await self.ingest() + + # Analyze intent + await self.analyze_intent() + + # Generate blueprint + await self.generate_blueprint() + + # Write and iterate chapters + await self.compile_manuscript() + + return self.state.manuscript + + def save_manuscript(self, output_path: Optional[Path] = None) -> Path: + """Save manuscript to file. + + Args: + output_path: Optional output path + + Returns: + Path to saved file + """ + if not self.state.manuscript: + raise ValueError("No manuscript to save. Run first.") + + output_path = output_path or self.config.output.output_dir / f"{self.state.manuscript.title.lower().replace(' ', '_')}.md" + output_path.parent.mkdir(parents=True, exist_ok=True) + + with open(output_path, "w") as f: + f.write(self.state.manuscript.to_markdown()) + + return output_path diff --git a/opus_orchestrator/schemas/__init__.py b/opus_orchestrator/schemas/__init__.py new file mode 100644 index 0000000..e2618ff --- /dev/null +++ b/opus_orchestrator/schemas/__init__.py @@ -0,0 +1,33 @@ +"""Opus Orchestrator schemas.""" + +from opus_orchestrator.schemas.book import ( + BookBlueprint, + BookIntent, + BookType, + Chapter, + ChapterBlueprint, + ChapterCritique, + ChapterDraft, + CritiqueScore, + Genre, + Manuscript, + NonfictionGenre, + RawContent, + Revision, +) + +__all__ = [ + "BookBlueprint", + "BookIntent", + "BookType", + "Chapter", + "ChapterBlueprint", + "ChapterCritique", + "ChapterDraft", + "CritiqueScore", + "Genre", + "Manuscript", + "NonfictionGenre", + "RawContent", + "Revision", +] diff --git a/opus_orchestrator/schemas/book.py b/opus_orchestrator/schemas/book.py new file mode 100644 index 0000000..feb1451 --- /dev/null +++ b/opus_orchestrator/schemas/book.py @@ -0,0 +1,218 @@ +"""Pydantic schemas for Opus Orchestrator.""" + +from __future__ import annotations + +from datetime import datetime +from enum import Enum +from typing import Any, Optional + +from pydantic import BaseModel, Field + + +class BookType(str, Enum): + """Type of book being generated.""" + + FICTION = "fiction" + NONFICTION = "nonfiction" + + +class Genre(str, Enum): + """Fiction genres.""" + + MYSTERY = "mystery" + ROMANCE = "romance" + THRILLER = "thriller" + FANTASY = "fantasy" + SCIENCE_FICTION = "science-fiction" + HORROR = "horror" + LITERARY = "literary" + OTHER = "other" + + +class NonfictionGenre(str, Enum): + """Nonfiction genres.""" + + BUSINESS = "business" + SELF_HELP = "self-help" + HISTORY = "history" + BIOGRAPHY = "biography" + SCIENCE = "science" + PHILOSOPHY = "philosophy" + MEMOIR = "memoir" + ACADEMIC = "academic" + OTHER = "other" + + +# --- Input Schemas --- + + +class BookIntent(BaseModel): + """User's intent for the book project.""" + + book_type: BookType + genre: Optional[str] = None + working_title: Optional[str] = None + target_audience: str = Field(description="Who is this book for?") + intended_outcome: str = Field(description="What should the final product achieve?") + tone: Optional[str] = None + target_word_count: int = Field(default=80000, ge=1000, le=500000) + special_instructions: Optional[str] = None + + +class RawContent(BaseModel): + """Raw content extracted from source.""" + + content_type: str = Field(description="Type: outline, notes, stream-of-consciousness, etc.") + text: str = Field(description="The raw content itself") + metadata: dict[str, Any] = Field(default_factory=dict) + + +# --- Blueprint Schemas --- + + +class ChapterBlueprint(BaseModel): + """Blueprint for a single chapter.""" + + chapter_number: int + title: str + summary: str = Field(description="Brief summary of chapter content") + word_count_target: int + pov_character: Optional[str] = None + key_events: list[str] = Field(default_factory=list) + themes: list[str] = Field(default_factory=list) + scenes: list[dict[str, Any]] = Field(default_factory=list) + + +class BookBlueprint(BaseModel): + """Complete blueprint for the book.""" + + title: str + subtitle: Optional[str] = None + genre: str + target_audience: str + target_word_count: int + structure: str = Field(description="Overall structural approach") + themes: list[str] = Field(default_factory=list) + tone: str + chapters: list[ChapterBlueprint] = Field(default_factory=list) + characters: list[dict[str, Any]] = Field(default_factory=list, description="For fiction") + world_elements: dict[str, Any] = Field(default_factory=dict, description="For fiction") + key_arguments: list[str] = Field(default_factory=list, description="For nonfiction") + + +# --- Agent Output Schemas --- + + +class ChapterDraft(BaseModel): + """A chapter draft from the writer agent.""" + + chapter_number: int + title: str + content: str + word_count: int + notes: Optional[str] = None + + +class CritiqueScore(BaseModel): + """Score from a critic agent.""" + + criterion: str + score: float = Field(ge=0.0, le=1.0) + strengths: list[str] = Field(default_factory=list) + weaknesses: list[str] = Field(default_factory=list) + suggestions: list[str] = Field(default_factory=list) + + +class ChapterCritique(BaseModel): + """Complete critique of a chapter.""" + + chapter_number: int + overall_score: float = Field(ge=0.0, le=1.0) + criteria_scores: list[CritiqueScore] + consensus_strengths: list[str] = Field(default_factory=list) + consensus_weaknesses: list[str] = Field(default_factory=list) + revision_priority: str = Field(description="major_revisions, minor_revisions, approved") + notes: Optional[str] = None + + +class Revision(BaseModel): + """Revision instructions for a chapter.""" + + chapter_number: int + priority: str = Field(description="major or minor") + changes_required: list[str] = Field(default_factory=list) + preserve_elements: list[str] = Field(default_factory=list) + notes: Optional[str] = None + + +# --- Final Output Schemas --- + + +class Chapter(BaseModel): + """Final chapter in the manuscript.""" + + chapter_number: int + title: str + content: str + word_count: int + + +class Manuscript(BaseModel): + """Complete manuscript.""" + + title: str + subtitle: Optional[str] = None + author: str = "Opus Orchestrator AI" + book_type: BookType + genre: str + chapters: list[Chapter] + total_word_count: int + frontmatter: dict[str, Any] = Field(default_factory=dict) + backmatter: dict[str, Any] = Field(default_factory=dict) + metadata: dict[str, Any] = Field(default_factory=dict) + generated_at: datetime = Field(default_factory=datetime.utcnow) + + def to_markdown(self) -> str: + """Convert to markdown format.""" + lines = [] + + # Frontmatter + if self.frontmatter.get("dedication"): + lines.append(f"*{self.frontmatter['dedication']}*\n") + if self.frontmatter.get("epigraph"): + lines.append(f"> {self.frontmatter['epigraph']}\n") + + # Title + lines.append(f"# {self.title}") + if self.subtitle: + lines.append(f"## {self.subtitle}") + lines.append(f"\n*by {self.author}*\n") + + # TOC + if self.frontmatter.get("include_toc", True): + lines.append("## Table of Contents\n") + for ch in self.chapters: + lines.append(f"{ch.chapter_number}. [{ch.title}](#chapter-{ch.chapter_number})") + lines.append("") + + # Chapters + for ch in self.chapters: + lines.append(f"## Chapter {ch.chapter_number}: {ch.title}\n") + lines.append(ch.content) + lines.append("") + + return "\n".join(lines) + + +# --- State Schemas --- + + +class IterationState(BaseModel): + """State for iteration tracking.""" + + round: int = 0 + chapter_number: int + critiques: list[ChapterCritique] = Field(default_factory=list) + current_score: float = 0.0 + approved: bool = False + revisions_requested: list[Revision] = Field(default_factory=list) diff --git a/opus_orchestrator/state.py b/opus_orchestrator/state.py new file mode 100644 index 0000000..44df160 --- /dev/null +++ b/opus_orchestrator/state.py @@ -0,0 +1,76 @@ +"""LangGraph state definitions for Opus Orchestrator.""" + +from typing import Any, Optional + +from pydantic import BaseModel, Field + +from opus_orchestrator.schemas import ( + BookBlueprint, + BookIntent, + Chapter, + ChapterCritique, + ChapterDraft, + Manuscript, + RawContent, + Revision, +) + + +class OpusState(BaseModel): + """Main state for the Opus Orchestrator graph. + + This state flows through the entire book generation pipeline, + accumulating data at each stage. + """ + + # --- Input Phase --- + repo_url: Optional[str] = None + raw_content: Optional[RawContent] = None + intent: Optional[BookIntent] = None + + # --- Blueprint Phase --- + blueprint: Optional[BookBlueprint] = None + + # --- Drafting Phase --- + current_chapter: int = 0 + drafts: dict[int, ChapterDraft] = Field(default_factory=dict) + + # --- Iteration Phase --- + iteration_round: int = 0 + critiques: dict[int, list[ChapterCritique]] = Field(default_factory=dict) + revisions: dict[int, list[Revision]] = Field(default_factory=dict) + + # --- Final Phase --- + manuscript: Optional[Manuscript] = None + + # --- Metadata --- + errors: list[str] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + progress: float = 0.0 # 0.0 to 1.0 + current_stage: str = "ingestion" # ingestion, blueprint, drafting, iteration, compilation, complete + + # --- Configuration --- + config: dict[str, Any] = Field(default_factory=dict) + + class Config: + arbitrary_types_allowed = True + + +# Define the graph nodes as functions +# These will be implemented in graph.py + + +def create_initial_state( + repo_url: str, + intent: BookIntent, + raw_content: Optional[RawContent] = None, + **config, +) -> OpusState: + """Create initial state for the orchestrator.""" + return OpusState( + repo_url=repo_url, + intent=intent, + raw_content=raw_content, + current_stage="ingestion", + config=config, + ) diff --git a/opus_orchestrator/utils/__init__.py b/opus_orchestrator/utils/__init__.py new file mode 100644 index 0000000..965ca96 --- /dev/null +++ b/opus_orchestrator/utils/__init__.py @@ -0,0 +1,3 @@ +"""Utility functions for Opus Orchestrator.""" + +__all__ = [] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d3fcb7c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,56 @@ +[project] +name = "opus-orchestrator-ai" +version = "0.1.0" +description = "Full-flow AI book generation orchestrator using LangGraph, CrewAI, AutoGen, and PydanticAI" +readme = "README.md" +requires-python = ">=3.11" +license = {text = "MIT"} +authors = [ + {name = "Mark Randall Havens", email = "mark@thefoldwithin.earth"} +] +keywords = ["ai", "writing", "book", "orchestrator", "crewai", "langgraph", "autogen"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +dependencies = [ + "langgraph>=0.2.0", + "crewai>=0.80.0", + "autogen>=0.4.0", + "pydantic-ai>=0.0.0", + "pydantic>=2.0.0", + "httpx>=0.27.0", + "pygithub>=2.0.0", + "pyyaml>=6.0", + "tiktoken>=0.7.0", + "markdown>=3.7", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0.0", + "pytest-asyncio>=0.23.0", + "ruff>=0.4.0", + "mypy>=1.8.0", + "pre-commit>=3.7.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.ruff] +target-version = "py311" +line-length = 100 + +[tool.ruff.lint] +select = ["E", "F", "I", "N", "W", "UP"] +ignore = ["E501"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..46816dd --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests package.""" diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py new file mode 100644 index 0000000..60bb5c1 --- /dev/null +++ b/tests/test_orchestrator.py @@ -0,0 +1,127 @@ +"""Tests for Opus Orchestrator.""" + +import pytest +from opus_orchestrator import OpusOrchestrator, BookType, BookIntent +from opus_orchestrator.schemas import RawContent, Chapter, Manuscript + + +@pytest.fixture +def basic_intent(): + return BookIntent( + book_type=BookType.FICTION, + genre="science-fiction", + target_audience="adult sci-fi readers", + intended_outcome="complete novel ~80k words", + tone="epic", + target_word_count=80000, + ) + + +@pytest.fixture +def basic_content(): + return RawContent( + content_type="outline", + text="A space explorer discovers a new civilization...", + ) + + +class TestOpusOrchestrator: + """Test suite for OpusOrchestrator.""" + + def test_init_fiction(self, basic_intent): + """Test initialization with fiction.""" + orch = OpusOrchestrator( + book_type="fiction", + genre="science-fiction", + target_audience="adult sci-fi readers", + intended_outcome="complete novel", + ) + + assert orch.book_type == BookType.FICTION + assert "architect" in orch.agents + assert "voice" in orch.agents + + def test_init_nonfiction(self): + """Test initialization with nonfiction.""" + orch = OpusOrchestrator( + book_type="nonfiction", + genre="business", + target_audience="professionals", + intended_outcome="complete business book", + ) + + assert orch.book_type == BookType.NONFICTION + assert "researcher" in orch.agents + assert "analyst" in orch.agents + + @pytest.mark.asyncio + async def test_ingest(self, basic_content): + """Test content ingestion.""" + orch = OpusOrchestrator( + book_type="fiction", + genre="fantasy", + target_audience="general", + intended_outcome="novel", + ) + + state = await orch.ingest(basic_content) + + assert state.raw_content == basic_content + assert state.current_stage == "ingestion" + + @pytest.mark.asyncio + async def test_generate_blueprint(self, basic_content): + """Test blueprint generation.""" + orch = OpusOrchestrator( + book_type="fiction", + genre="mystery", + target_audience="general", + intended_outcome="novel", + ) + + await orch.ingest(basic_content) + blueprint = await orch.generate_blueprint() + + assert blueprint.title == "Untitled" + assert blueprint.target_word_count == 80000 + + +class TestSchemas: + """Test schema validation.""" + + def test_book_intent_fiction(self): + """Test BookIntent for fiction.""" + intent = BookIntent( + book_type=BookType.FICTION, + genre="thriller", + target_audience="adult thriller readers", + intended_outcome="complete thriller novel", + target_word_count=90000, + ) + + assert intent.book_type == BookType.FICTION + assert intent.target_word_count == 90000 + + def test_manuscript_to_markdown(self): + """Test manuscript markdown conversion.""" + manuscript = Manuscript( + title="Test Book", + subtitle="A Test", + book_type=BookType.FICTION, + genre="fantasy", + chapters=[ + Chapter(chapter_number=1, title="The Beginning", content="Content 1", word_count=1000), + Chapter(chapter_number=2, title="The Middle", content="Content 2", word_count=1500), + ], + total_word_count=2500, + frontmatter={"include_toc": True, "dedication": "To test"}, + ) + + md = manuscript.to_markdown() + + assert "# Test Book" in md + assert "## A Test" in md + assert "## Table of Contents" in md + assert "Chapter 1: The Beginning" in md + assert "Chapter 2: The Middle" in md + assert "*To test*" in md