Add multiple story frameworks
- Snowflake Method (already had) - Three-Act Structure - Save the Cat (Blake Snyder) - Hero's Journey (Joseph Campbell) - Story Circle (Dan Harmon) - The 7-Point Plot (The Pantone) - Fichtean Curve Pass framework='hero-journey' or 'save-the-cat' etc. to use.
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
"""Story frameworks for Opus Orchestrator.
|
||||
|
||||
Implements multiple narrative structure frameworks:
|
||||
- Snowflake Method
|
||||
- Three-Act Structure
|
||||
- Save the Cat (Blake Snyder)
|
||||
- Hero's Journey (Joseph Campbell)
|
||||
- Story Circle (Dan Harmon)
|
||||
- The 7-Point Plot (The Pantone)
|
||||
- Fichtean Curve
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
|
||||
class StoryFramework(str, Enum):
|
||||
"""Available story frameworks."""
|
||||
|
||||
SNOWFLAKE = "snowflake"
|
||||
THREE_ACT = "three-act"
|
||||
SAVE_THE_CAT = "save-the-cat"
|
||||
HERO_JOURNEY = "hero-journey"
|
||||
STORY_CIRCLE = "story-circle"
|
||||
SEVEN_POINT = "seven-point"
|
||||
FICHTEAN = "fichtean"
|
||||
|
||||
|
||||
# Framework descriptions and prompts
|
||||
|
||||
FRAMEWORKS = {
|
||||
StoryFramework.SNOWFLAKE: {
|
||||
"name": "Snowflake Method",
|
||||
"description": "Fractal expansion from sentence to paragraph to full novel",
|
||||
"stages": [
|
||||
"One sentence summary",
|
||||
"One paragraph outline",
|
||||
"Character sheets",
|
||||
"Four-page outline",
|
||||
"Detailed character charts",
|
||||
"Scene list",
|
||||
"Scene descriptions",
|
||||
],
|
||||
},
|
||||
|
||||
StoryFramework.THREE_ACT: {
|
||||
"name": "Three-Act Structure",
|
||||
"description": "Classic setup, confrontation, resolution structure",
|
||||
"stages": [
|
||||
"Act I: Setup - Introduce protagonist, status quo, inciting incident",
|
||||
"Act I: Break into Two - Commit to journey",
|
||||
"Act II, Part 1: Fun and Games - Promise of premise",
|
||||
"Act II, Part 1: Midpoint - Stakes raise",
|
||||
"Act II, Part 2: Bad Guys Close In - External pressure",
|
||||
"Act II, Part 2: All Is Lost - Lowest point",
|
||||
"Act III: Finale - Climax and resolution",
|
||||
],
|
||||
},
|
||||
|
||||
StoryFramework.SAVE_THE_CAT: {
|
||||
"name": "Save the Cat",
|
||||
"description": "Blake Snyder's 15-beat screenwriting structure",
|
||||
"beats": [
|
||||
("Opening Image", "Visual hook that sets the tone"),
|
||||
("Theme Stated", "Someone states the theme"),
|
||||
("Setup", "Normal world, introduce key players"),
|
||||
("Catalyst", "Life-changing event"),
|
||||
("Debate", "Protagonist resists the call"),
|
||||
("Break Into Two", "Commit to the journey"),
|
||||
("B Story", "Subplot begins"),
|
||||
("Fun and Games", "Promise of the premise"),
|
||||
("Midpoint", "Stakes are raised"),
|
||||
("Bad Guys Close In", "External pressure increases"),
|
||||
("All Is Lost", "Darkest moment"),
|
||||
("Dark Night of the Soul", "Emotional nadir"),
|
||||
("Break Into Three", "Solution found"),
|
||||
("Finale", "Final confrontation"),
|
||||
("Final Image", "Changed world"),
|
||||
],
|
||||
},
|
||||
|
||||
StoryFramework.HERO_JOURNEY: {
|
||||
"name": "Hero's Journey",
|
||||
"description": "Joseph Campbell's monomyth - 12 stages",
|
||||
"beats": [
|
||||
("Ordinary World", "Hero's normal life before the adventure"),
|
||||
("Call to Adventure", "Challenge or quest presented"),
|
||||
("Refusal of the Call", "Hero hesitates or declines"),
|
||||
("Meeting the Mentor", "Guide appears with wisdom/tools"),
|
||||
("Crossing the Threshold", "Hero commits, enters special world"),
|
||||
("Tests, Allies, Enemies", "Hero faces challenges, makes friends/foes"),
|
||||
("Approach to Inmost Cave", "Hero prepares for major challenge"),
|
||||
("Ordeal", "Hero faces greatest challenge, death/rebirth"),
|
||||
("Reward", "Hero gains prize/knowledge"),
|
||||
("The Road Back", "Hero begins return journey"),
|
||||
("Resurrection", "Final test, hero transformed"),
|
||||
("Return with Elixir", "Hero returns, transformed"),
|
||||
],
|
||||
},
|
||||
|
||||
StoryFramework.STORY_CIRCLE: {
|
||||
"name": "Story Circle",
|
||||
"description": "Dan Harmon's 8-step circular structure",
|
||||
"beats": [
|
||||
("You", "Character in their comfort zone"),
|
||||
("Need", "Character feels something is missing"),
|
||||
("Go", "Character enters unfamiliar situation"),
|
||||
("Adapt", "Character adjusts to new world"),
|
||||
("Get", "Character gains what they sought"),
|
||||
("Return", "Character returns home"),
|
||||
("Change", "Character is transformed"),
|
||||
("Result", "Character's life is different"),
|
||||
],
|
||||
},
|
||||
|
||||
StoryFramework.SEVEN_POINT: {
|
||||
"name": "The 7-Point Plot",
|
||||
"description": "The Pantone method - 7 key plot points",
|
||||
"beats": [
|
||||
("Hook", "Opening that grabs attention"),
|
||||
("Plot Turn 1", "First major change, inciting incident"),
|
||||
("Pinch Point 1", "Pressure from antagonist"),
|
||||
("Midpoint", "Character commits to action"),
|
||||
("Pinch Point 2", "Increased pressure, stakes raise"),
|
||||
("Plot Turn 2", "Major reversal, everything changes"),
|
||||
("Resolution", "Final confrontation, new equilibrium"),
|
||||
],
|
||||
},
|
||||
|
||||
StoryFramework.FICHTEAN: {
|
||||
"name": "Fichtean Curve",
|
||||
"description": "Progressive rising action - series of crises",
|
||||
"beats": [
|
||||
("Inciting Incident", "Chain of events begins"),
|
||||
("Rising Action 1", "First crisis"),
|
||||
("Rising Action 2", "Second crisis"),
|
||||
("Rising Action 3", "Third crisis"),
|
||||
("Climax", "Maximum tension"),
|
||||
("Falling Action", "Aftermath"),
|
||||
("Resolution", "New status quo"),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_framework_prompt(framework: StoryFramework) -> str:
|
||||
"""Get the system prompt for a framework."""
|
||||
|
||||
if framework == StoryFramework.SAVE_THE_CAT:
|
||||
return """You are an expert in Save the Cat story structure (Blake Snyder).
|
||||
|
||||
The 15 beats of Save the Cat:
|
||||
1. Opening Image - Visual hook, tone setter
|
||||
2. Theme Stated - Someone states the theme
|
||||
3. Setup - Normal world, introduce characters
|
||||
4. Catalyst - Life-changing event
|
||||
5. Debate - Protagonist resists the call
|
||||
6. Break Into Two - Commit to journey
|
||||
7. B Story - Subplot begins
|
||||
8. Fun and Games - Promise of the premise
|
||||
9. Midpoint - Stakes raise
|
||||
10. Bad Guys Close In - External pressure
|
||||
11. All Is Lost - Darkest moment
|
||||
12. Dark Night of the Soul - Emotional nadirs
|
||||
13. Break Into Three - Solution found
|
||||
14. Finale - Final confrontation
|
||||
15. Final Image - Changed world
|
||||
|
||||
Use these beats to structure compelling narratives."""
|
||||
|
||||
elif framework == StoryFramework.HERO_JOURNEY:
|
||||
return """You are an expert in the Hero's Journey (Joseph Campbell's monomyth).
|
||||
|
||||
The 12 stages:
|
||||
1. Ordinary World - Hero's normal life
|
||||
2. Call to Adventure - Challenge presented
|
||||
3. Refusal of the Call - Hero hesitates
|
||||
4. Meeting the Mentor - Guide appears
|
||||
5. Crossing the Threshold - Enter special world
|
||||
6. Tests, Allies, Enemies - Face challenges
|
||||
7. Approach to Inmost Cave - Prepare for major challenge
|
||||
8. Ordeal - Greatest challenge, death/rebirth
|
||||
9. Reward - Gain prize/knowledge
|
||||
10. The Road Back - Begin return
|
||||
11. Resurrection - Final test, transformation
|
||||
12. Return with Elixir - Return transformed
|
||||
|
||||
Use these stages to structure mythic narratives."""
|
||||
|
||||
elif framework == StoryFramework.STORY_CIRCLE:
|
||||
return """You are an expert in Dan Harmon's Story Circle.
|
||||
|
||||
The 8 steps:
|
||||
1. You - Character in comfort zone
|
||||
2. Need - Something missing
|
||||
3. Go - Enter unfamiliar situation
|
||||
4. Adapt - Adjust to new world
|
||||
5. Get - Gain what sought
|
||||
6. Return - Return home
|
||||
7. Change - Transformed
|
||||
8. Result - Life different
|
||||
|
||||
Use this circular structure for balanced narratives."""
|
||||
|
||||
elif framework == StoryFramework.SEVEN_POINT:
|
||||
return """You are an expert in The 7-Point Plot (The Pantone).
|
||||
|
||||
The 7 plot points:
|
||||
1. Hook - Opening grab
|
||||
2. Plot Turn 1 - First major change
|
||||
3. Pinch Point 1 - Antagonist pressure
|
||||
4. Midpoint - Commit to action
|
||||
5. Pinch Point 2 - Stakes raise
|
||||
6. Plot Turn 2 - Major reversal
|
||||
7. Resolution - New equilibrium
|
||||
|
||||
Use this for tight, event-driven plots."""
|
||||
|
||||
elif framework == StoryFramework.FICHTEAN:
|
||||
return """You are an expert in the Fichtean Curve.
|
||||
|
||||
Progressive crisis structure:
|
||||
1. Inciting Incident - Chain of events begins
|
||||
2. Rising Action 1 - First crisis
|
||||
3. Rising Action 2 - Second crisis (bigger)
|
||||
4. Rising Action 3 - Third crisis (biggest)
|
||||
5. Climax - Maximum tension
|
||||
6. Falling Action - Aftermath
|
||||
7. Resolution - New status quo
|
||||
|
||||
Use this for action-driven, tension-building narratives."""
|
||||
|
||||
elif framework == StoryFramework.THREE_ACT:
|
||||
return """You are an expert in Three-Act Structure.
|
||||
|
||||
Classic narrative structure:
|
||||
ACT I - SETUP
|
||||
- Opening Image
|
||||
- Theme Stated
|
||||
- Setup (normal world)
|
||||
- Catalyst (inciting incident)
|
||||
- Debate (protagonist resists)
|
||||
- Break Into Two (commit to journey)
|
||||
|
||||
ACT II - CONFRONTATION
|
||||
- B Story (subplot begins)
|
||||
- Fun and Games (promise of premise)
|
||||
- Midpoint (stakes raise)
|
||||
- Bad Guys Close In (external pressure)
|
||||
- All Is Lost (lowest point)
|
||||
- Dark Night of the Soul (internal reckoning)
|
||||
- Break Into Three (solution found)
|
||||
|
||||
ACT III - RESOLUTION
|
||||
- Finale (climax)
|
||||
- Final Image (changed world)
|
||||
|
||||
Use this traditional structure for any genre."""
|
||||
|
||||
else:
|
||||
return "You are an expert story architect."
|
||||
|
||||
|
||||
def get_framework_for_genre(genre: str) -> list[StoryFramework]:
|
||||
"""Suggest frameworks based on genre."""
|
||||
|
||||
suggestions = {
|
||||
"fantasy": [StoryFramework.HERO_JOURNEY, StoryFramework.SNOWFLAKE],
|
||||
"science-fiction": [StoryFramework.SNOWFLAKE, StoryFramework.SAVE_THE_CAT],
|
||||
"thriller": [StoryFramework.SAVE_THE_CAT, StoryFramework.SEVEN_POINT, StoryFramework.FICHTEAN],
|
||||
"horror": [StoryFramework.HERO_JOURNEY, StoryFramework.FICHTEAN],
|
||||
"romance": [StoryFramework.STORY_CIRCLE, StoryFramework.SAVE_THE_CAT],
|
||||
"mystery": [StoryFramework.SEVEN_POINT, StoryFramework.THREE_ACT],
|
||||
"literary": [StoryFramework.SNOWFLAKE, StoryFramework.HERO_JOURNEY],
|
||||
"adventure": [StoryFramework.HERO_JOURNEY, StoryFramework.FICHTEAN],
|
||||
}
|
||||
|
||||
return suggestions.get(genre.lower(), [StoryFramework.THREE_ACT, StoryFramework.SNOWFLAKE])
|
||||
@@ -1,14 +1,13 @@
|
||||
"""Main Opus Orchestrator - Snowflake Method Implementation.
|
||||
"""Main Opus Orchestrator - Snowflake Method Implementation with Multiple Frameworks.
|
||||
|
||||
Full pipeline following the Snowflake Method for book writing:
|
||||
1. One sentence (concept)
|
||||
2. One paragraph (outline)
|
||||
3. Character sheets
|
||||
4. Four-page outline
|
||||
5. Detailed character charts
|
||||
6. Scene list
|
||||
7. Scene descriptions
|
||||
8. First draft
|
||||
Full pipeline supporting multiple story frameworks:
|
||||
- Snowflake Method (fractal expansion)
|
||||
- Three-Act Structure
|
||||
- Save the Cat (Blake Snyder)
|
||||
- Hero's Journey (Joseph Campbell)
|
||||
- Story Circle (Dan Harmon)
|
||||
- The 7-Point Plot (The Pantone)
|
||||
- Fichtean Curve
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -35,6 +34,12 @@ from opus_orchestrator.agents.nonfiction import (
|
||||
ResearcherAgent,
|
||||
)
|
||||
from opus_orchestrator.config import OpusConfig, get_config
|
||||
from opus_orchestrator.frameworks import (
|
||||
StoryFramework,
|
||||
FRAMEWORKS,
|
||||
get_framework_for_genre,
|
||||
get_framework_prompt,
|
||||
)
|
||||
from opus_orchestrator.schemas import (
|
||||
BookBlueprint,
|
||||
BookIntent,
|
||||
@@ -50,7 +55,7 @@ from opus_orchestrator.state import OpusState
|
||||
|
||||
|
||||
class OpusOrchestrator:
|
||||
"""Main orchestrator implementing the Snowflake Method."""
|
||||
"""Main orchestrator implementing multiple story frameworks."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -61,9 +66,23 @@ class OpusOrchestrator:
|
||||
intended_outcome: str = "complete novel",
|
||||
tone: Optional[str] = None,
|
||||
target_word_count: int = 80000,
|
||||
framework: str = "snowflake",
|
||||
config: Optional[OpusConfig] = None,
|
||||
):
|
||||
"""Initialize the Opus Orchestrator with Snowflake Method."""
|
||||
"""Initialize the Opus Orchestrator with selectable framework.
|
||||
|
||||
Args:
|
||||
repo_url: GitHub URL for content
|
||||
book_type: "fiction" or "nonfiction"
|
||||
genre: Genre (for framework suggestions)
|
||||
target_audience: Who is this for
|
||||
intended_outcome: What to produce
|
||||
tone: Desired tone
|
||||
target_word_count: Target length
|
||||
framework: Story framework to use (snowflake, three-act, save-the-cat,
|
||||
hero-journey, story-circle, seven-point, fichtean)
|
||||
config: Optional config override
|
||||
"""
|
||||
self.config = config or get_config()
|
||||
|
||||
if not self.config.agent.api_key:
|
||||
@@ -71,6 +90,19 @@ class OpusOrchestrator:
|
||||
|
||||
self.book_type = BookType(book_type.lower())
|
||||
self.repo_url = repo_url
|
||||
|
||||
# Handle framework
|
||||
if isinstance(framework, str):
|
||||
try:
|
||||
self.framework = StoryFramework(framework.lower())
|
||||
except ValueError:
|
||||
# Default to snowflake if invalid
|
||||
self.framework = StoryFramework.SNOWFLAKE
|
||||
else:
|
||||
self.framework = framework
|
||||
|
||||
# Get framework info
|
||||
self.framework_info = FRAMEWORKS.get(self.framework, FRAMEWORKS[StoryFramework.SNOWFLAKE])
|
||||
|
||||
self.intent = BookIntent(
|
||||
book_type=self.book_type,
|
||||
@@ -171,23 +203,19 @@ Write ONE compelling sentence that captures the entire story.
|
||||
return self.one_sentence
|
||||
|
||||
async def snowflake_stage_2(self) -> str:
|
||||
"""Stage 2: One paragraph summary.
|
||||
"""Stage 2: One paragraph outline (framework-dependent).
|
||||
|
||||
Expand the one sentence to a paragraph with setup, 3 acts, and resolution.
|
||||
Uses the selected story framework.
|
||||
"""
|
||||
print("❄️ SNOWFLAKE STAGE 2: One paragraph outline...")
|
||||
print(f"❄️ SNOWFLAKE STAGE 2: One paragraph outline ({self.framework_info['name']})...")
|
||||
|
||||
# Get framework-specific prompt
|
||||
framework_system_prompt = get_framework_prompt(self.framework)
|
||||
|
||||
user_prompt = f"""Expand this one-sentence summary into a full one-paragraph story outline.
|
||||
|
||||
Include:
|
||||
- Opening image (the "before" state)
|
||||
- Setup (normal world, who the protagonist is)
|
||||
- Catalyst (what changes everything)
|
||||
- Rising action (attempts to solve the problem)
|
||||
- Midpoint (major twist or revelation)
|
||||
- Complications (things get worse)
|
||||
- Crisis (lowest point)
|
||||
- Resolution (how it ends)
|
||||
Use the {self.framework_info['name']} framework: {self.framework_info.get('description', '')}
|
||||
|
||||
## One sentence:
|
||||
{self.one_sentence}
|
||||
@@ -196,7 +224,7 @@ Include:
|
||||
Write one detailed paragraph (4-8 sentences) that tells the complete story arc.
|
||||
"""
|
||||
response = await self.agents["architect"].call_llm(
|
||||
system_prompt="You are an expert story architect. Create detailed, act-structured outlines.",
|
||||
system_prompt=framework_system_prompt,
|
||||
user_prompt=user_prompt,
|
||||
)
|
||||
|
||||
@@ -316,27 +344,31 @@ Write comprehensive, detailed character charts.
|
||||
return self.character_charts
|
||||
|
||||
async def snowflake_stage_6(self) -> str:
|
||||
"""Stage 6: Scene list.
|
||||
"""Stage 6: Scene list (framework-dependent).
|
||||
|
||||
Create a list of all scenes needed (like index cards).
|
||||
Create a list of all scenes using the selected framework.
|
||||
"""
|
||||
print("❄️ SNOWFLAKE STAGE 6: Scene list...")
|
||||
print(f"❄️ SNOWFLAKE STAGE 6: Scene list ({self.framework_info['name']})...")
|
||||
|
||||
# Get framework-specific prompt
|
||||
framework_system_prompt = get_framework_prompt(self.framework)
|
||||
|
||||
words_per_scene = 1500 # Average scene length
|
||||
num_scenes = max(10, self.intent.target_word_count // words_per_scene)
|
||||
|
||||
user_prompt = f"""Create a complete SCENE LIST for this story.
|
||||
|
||||
For each scene, provide:
|
||||
- Scene number
|
||||
- POV character
|
||||
- Setting/location
|
||||
- What happens (one line)
|
||||
- Purpose (advances plot? reveals character? builds world?)
|
||||
- Chapter placement
|
||||
# Get framework beats if available
|
||||
framework_beats = ""
|
||||
if "beats" in self.framework_info:
|
||||
framework_beats = f"\n\n## Framework Beats:\n"
|
||||
for beat_name, beat_desc in self.framework_info["beats"]:
|
||||
framework_beats += f"- {beat_name}: {beat_desc}\n"
|
||||
|
||||
user_prompt = f"""Create a complete SCENE LIST for this story using the {self.framework_info['name']}.
|
||||
|
||||
Target: approximately {num_scenes} scenes for a {self.intent.target_word_count:,} word novel.
|
||||
|
||||
{framework_beats}
|
||||
|
||||
## Four-page outline:
|
||||
{self.four_page_outline}
|
||||
|
||||
@@ -347,7 +379,7 @@ Target: approximately {num_scenes} scenes for a {self.intent.target_word_count:,
|
||||
Create a comprehensive scene list with all scenes needed.
|
||||
"""
|
||||
response = await self.agents["architect"].call_llm(
|
||||
system_prompt="You are an expert story architect. Create detailed scene lists.",
|
||||
system_prompt=framework_system_prompt,
|
||||
user_prompt=user_prompt,
|
||||
)
|
||||
|
||||
@@ -545,7 +577,7 @@ Make it vivid, engaging, and true to the characters.
|
||||
themes=[],
|
||||
tone=self.intent.tone or "neutral",
|
||||
chapters=[
|
||||
BookBlueprint.model_construct(
|
||||
ChapterBlueprint(
|
||||
chapter_number=i,
|
||||
title=f"Chapter {i}",
|
||||
summary=f"Chapter {i}",
|
||||
@@ -595,18 +627,21 @@ Make it vivid, engaging, and true to the characters.
|
||||
# =========================================================================
|
||||
|
||||
async def run(self) -> Manuscript:
|
||||
"""Run the full Snowflake Method pipeline."""
|
||||
"""Run the full pipeline with selected framework."""
|
||||
framework_name = self.framework_info.get("name", "Unknown")
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print("❄️ OPUS ORCHESTRATOR - SNOWFLAKE METHOD")
|
||||
print(f"{'='*60}\n")
|
||||
print(f"❄️ OPUS ORCHESTRATOR - {framework_name.upper()}")
|
||||
print(f"{'='*60}")
|
||||
print(f"Framework: {self.framework_info.get('description', '')}\n")
|
||||
|
||||
await self.ingest()
|
||||
|
||||
# Pre-writing stages (Snowflake 1-7)
|
||||
# Pre-writing stages
|
||||
await self.snowflake_stage_1() # One sentence
|
||||
await self.snowflake_stage_2() # One paragraph
|
||||
await self.snowflake_stage_2() # One paragraph/outline with framework
|
||||
await self.snowflake_stage_3() # Character sheets
|
||||
await self.snowflake_stage_4() # Four-page outline
|
||||
await self.snowflake_stage_4() # Expanded outline
|
||||
await self.snowflake_stage_5() # Detailed character charts
|
||||
await self.snowflake_stage_6() # Scene list
|
||||
await self.snowflake_stage_7() # Scene descriptions
|
||||
@@ -621,11 +656,12 @@ Make it vivid, engaging, and true to the characters.
|
||||
manuscript = await self.compile_manuscript()
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print("✅ SNOWFLAKE COMPLETE!")
|
||||
print("✅ COMPLETE!")
|
||||
print(f"{'='*60}")
|
||||
print(f"📖 Title: {manuscript.title}")
|
||||
print(f"📄 Words: {manuscript.total_word_count:,}")
|
||||
print(f"📑 Chapters: {len(manuscript.chapters)}")
|
||||
print(f"🎯 Framework: {framework_name}")
|
||||
|
||||
return manuscript
|
||||
|
||||
|
||||
Reference in New Issue
Block a user