postwriter 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. postwriter-0.1.0/PKG-INFO +189 -0
  2. postwriter-0.1.0/README.md +167 -0
  3. postwriter-0.1.0/pyproject.toml +57 -0
  4. postwriter-0.1.0/src/postwriter/__init__.py +2 -0
  5. postwriter-0.1.0/src/postwriter/__main__.py +6 -0
  6. postwriter-0.1.0/src/postwriter/agents/__init__.py +6 -0
  7. postwriter-0.1.0/src/postwriter/agents/architect.py +118 -0
  8. postwriter-0.1.0/src/postwriter/agents/base.py +278 -0
  9. postwriter-0.1.0/src/postwriter/agents/branch_profiles.py +167 -0
  10. postwriter-0.1.0/src/postwriter/agents/chapter_planner.py +78 -0
  11. postwriter-0.1.0/src/postwriter/agents/character_designer.py +86 -0
  12. postwriter-0.1.0/src/postwriter/agents/context.py +9 -0
  13. postwriter-0.1.0/src/postwriter/agents/local_rewriter.py +62 -0
  14. postwriter-0.1.0/src/postwriter/agents/promise_seeder.py +63 -0
  15. postwriter-0.1.0/src/postwriter/agents/scene_planner.py +91 -0
  16. postwriter-0.1.0/src/postwriter/agents/scene_writer.py +80 -0
  17. postwriter-0.1.0/src/postwriter/agents/style_builder.py +84 -0
  18. postwriter-0.1.0/src/postwriter/canon/__init__.py +7 -0
  19. postwriter-0.1.0/src/postwriter/canon/events.py +62 -0
  20. postwriter-0.1.0/src/postwriter/canon/mutations.py +65 -0
  21. postwriter-0.1.0/src/postwriter/canon/slicer.py +263 -0
  22. postwriter-0.1.0/src/postwriter/canon/store.py +316 -0
  23. postwriter-0.1.0/src/postwriter/cli/__init__.py +1 -0
  24. postwriter-0.1.0/src/postwriter/cli/app.py +391 -0
  25. postwriter-0.1.0/src/postwriter/cli/bootstrap.py +140 -0
  26. postwriter-0.1.0/src/postwriter/cli/dashboards/__init__.py +1 -0
  27. postwriter-0.1.0/src/postwriter/cli/dashboards/manuscript.py +102 -0
  28. postwriter-0.1.0/src/postwriter/cli/dashboards/scene.py +103 -0
  29. postwriter-0.1.0/src/postwriter/cli/display.py +210 -0
  30. postwriter-0.1.0/src/postwriter/cli/execution.py +35 -0
  31. postwriter-0.1.0/src/postwriter/config.py +69 -0
  32. postwriter-0.1.0/src/postwriter/context/__init__.py +5 -0
  33. postwriter-0.1.0/src/postwriter/context/condenser.py +166 -0
  34. postwriter-0.1.0/src/postwriter/context/loader.py +320 -0
  35. postwriter-0.1.0/src/postwriter/db/__init__.py +5 -0
  36. postwriter-0.1.0/src/postwriter/db/migrations/README +1 -0
  37. postwriter-0.1.0/src/postwriter/db/migrations/env.py +58 -0
  38. postwriter-0.1.0/src/postwriter/db/migrations/script.py.mako +28 -0
  39. postwriter-0.1.0/src/postwriter/db/migrations/versions/a68fbb4eb8ce_initial_schema.py +412 -0
  40. postwriter-0.1.0/src/postwriter/db/session.py +46 -0
  41. postwriter-0.1.0/src/postwriter/devices/__init__.py +6 -0
  42. postwriter-0.1.0/src/postwriter/devices/annotation.py +149 -0
  43. postwriter-0.1.0/src/postwriter/devices/detectors/__init__.py +6 -0
  44. postwriter-0.1.0/src/postwriter/devices/detectors/figurative.py +89 -0
  45. postwriter-0.1.0/src/postwriter/devices/detectors/lexical.py +219 -0
  46. postwriter-0.1.0/src/postwriter/devices/detectors/narrative.py +106 -0
  47. postwriter-0.1.0/src/postwriter/devices/detectors/rhythm.py +151 -0
  48. postwriter-0.1.0/src/postwriter/devices/imagery_domains.py +137 -0
  49. postwriter-0.1.0/src/postwriter/devices/overuse_rules.py +143 -0
  50. postwriter-0.1.0/src/postwriter/devices/taxonomy.py +74 -0
  51. postwriter-0.1.0/src/postwriter/errors.py +67 -0
  52. postwriter-0.1.0/src/postwriter/export/__init__.py +7 -0
  53. postwriter-0.1.0/src/postwriter/export/epub.py +256 -0
  54. postwriter-0.1.0/src/postwriter/export/json_export.py +125 -0
  55. postwriter-0.1.0/src/postwriter/export/markdown.py +57 -0
  56. postwriter-0.1.0/src/postwriter/export/report.py +147 -0
  57. postwriter-0.1.0/src/postwriter/graphs/__init__.py +6 -0
  58. postwriter-0.1.0/src/postwriter/graphs/metrics.py +166 -0
  59. postwriter-0.1.0/src/postwriter/graphs/temporal.py +99 -0
  60. postwriter-0.1.0/src/postwriter/llm/__init__.py +6 -0
  61. postwriter-0.1.0/src/postwriter/llm/budget.py +61 -0
  62. postwriter-0.1.0/src/postwriter/llm/client.py +208 -0
  63. postwriter-0.1.0/src/postwriter/logging_config.py +108 -0
  64. postwriter-0.1.0/src/postwriter/models/__init__.py +57 -0
  65. postwriter-0.1.0/src/postwriter/models/analytics.py +129 -0
  66. postwriter-0.1.0/src/postwriter/models/base.py +36 -0
  67. postwriter-0.1.0/src/postwriter/models/characters.py +87 -0
  68. postwriter-0.1.0/src/postwriter/models/core.py +166 -0
  69. postwriter-0.1.0/src/postwriter/models/events.py +35 -0
  70. postwriter-0.1.0/src/postwriter/models/narrative.py +141 -0
  71. postwriter-0.1.0/src/postwriter/models/style.py +51 -0
  72. postwriter-0.1.0/src/postwriter/orchestrator/__init__.py +1 -0
  73. postwriter-0.1.0/src/postwriter/orchestrator/branch_manager.py +110 -0
  74. postwriter-0.1.0/src/postwriter/orchestrator/checkpoint.py +117 -0
  75. postwriter-0.1.0/src/postwriter/orchestrator/critic_runner.py +72 -0
  76. postwriter-0.1.0/src/postwriter/orchestrator/engine.py +152 -0
  77. postwriter-0.1.0/src/postwriter/orchestrator/error_handling.py +71 -0
  78. postwriter-0.1.0/src/postwriter/orchestrator/global_revision.py +293 -0
  79. postwriter-0.1.0/src/postwriter/orchestrator/planner.py +530 -0
  80. postwriter-0.1.0/src/postwriter/orchestrator/policies.py +32 -0
  81. postwriter-0.1.0/src/postwriter/orchestrator/scene_loop.py +278 -0
  82. postwriter-0.1.0/src/postwriter/profiles.py +80 -0
  83. postwriter-0.1.0/src/postwriter/project.py +74 -0
  84. postwriter-0.1.0/src/postwriter/prompts/__init__.py +5 -0
  85. postwriter-0.1.0/src/postwriter/prompts/loader.py +41 -0
  86. postwriter-0.1.0/src/postwriter/prompts/templates/__init__.py +0 -0
  87. postwriter-0.1.0/src/postwriter/prompts/templates/architect_premise.j2 +70 -0
  88. postwriter-0.1.0/src/postwriter/prompts/templates/architect_spine.j2 +44 -0
  89. postwriter-0.1.0/src/postwriter/prompts/templates/chapter_planner.j2 +60 -0
  90. postwriter-0.1.0/src/postwriter/prompts/templates/character_designer.j2 +54 -0
  91. postwriter-0.1.0/src/postwriter/prompts/templates/local_rewriter.j2 +36 -0
  92. postwriter-0.1.0/src/postwriter/prompts/templates/promise_seeder.j2 +37 -0
  93. postwriter-0.1.0/src/postwriter/prompts/templates/scene_planner.j2 +77 -0
  94. postwriter-0.1.0/src/postwriter/prompts/templates/scene_writer.j2 +70 -0
  95. postwriter-0.1.0/src/postwriter/prompts/templates/style_builder.j2 +49 -0
  96. postwriter-0.1.0/src/postwriter/py.typed +0 -0
  97. postwriter-0.1.0/src/postwriter/repair/__init__.py +6 -0
  98. postwriter-0.1.0/src/postwriter/repair/actions.py +22 -0
  99. postwriter-0.1.0/src/postwriter/repair/planner.py +178 -0
  100. postwriter-0.1.0/src/postwriter/revision/__init__.py +5 -0
  101. postwriter-0.1.0/src/postwriter/revision/arc_audit.py +141 -0
  102. postwriter-0.1.0/src/postwriter/revision/backward_propagation.py +259 -0
  103. postwriter-0.1.0/src/postwriter/revision/base.py +29 -0
  104. postwriter-0.1.0/src/postwriter/revision/device_ecology.py +93 -0
  105. postwriter-0.1.0/src/postwriter/revision/promise_audit.py +68 -0
  106. postwriter-0.1.0/src/postwriter/revision/rhythm_audit.py +85 -0
  107. postwriter-0.1.0/src/postwriter/revision/theme_overstatement.py +139 -0
  108. postwriter-0.1.0/src/postwriter/scoring/__init__.py +5 -0
  109. postwriter-0.1.0/src/postwriter/scoring/comparison.py +100 -0
  110. postwriter-0.1.0/src/postwriter/scoring/device_balance.py +28 -0
  111. postwriter-0.1.0/src/postwriter/scoring/thresholds.py +62 -0
  112. postwriter-0.1.0/src/postwriter/scoring/vectors.py +110 -0
  113. postwriter-0.1.0/src/postwriter/types.py +313 -0
  114. postwriter-0.1.0/src/postwriter/validation/__init__.py +9 -0
  115. postwriter-0.1.0/src/postwriter/validation/base.py +135 -0
  116. postwriter-0.1.0/src/postwriter/validation/hard/__init__.py +16 -0
  117. postwriter-0.1.0/src/postwriter/validation/hard/banned_patterns.py +92 -0
  118. postwriter-0.1.0/src/postwriter/validation/hard/continuity.py +110 -0
  119. postwriter-0.1.0/src/postwriter/validation/hard/knowledge.py +84 -0
  120. postwriter-0.1.0/src/postwriter/validation/hard/pov.py +74 -0
  121. postwriter-0.1.0/src/postwriter/validation/hard/timeline.py +75 -0
  122. postwriter-0.1.0/src/postwriter/validation/soft/__init__.py +25 -0
  123. postwriter-0.1.0/src/postwriter/validation/soft/base.py +111 -0
  124. postwriter-0.1.0/src/postwriter/validation/soft/dialogue.py +25 -0
  125. postwriter-0.1.0/src/postwriter/validation/soft/emotion.py +23 -0
  126. postwriter-0.1.0/src/postwriter/validation/soft/prose_vitality.py +38 -0
  127. postwriter-0.1.0/src/postwriter/validation/soft/redundancy.py +30 -0
  128. postwriter-0.1.0/src/postwriter/validation/soft/scene_purpose.py +23 -0
  129. postwriter-0.1.0/src/postwriter/validation/soft/symbolic_restraint.py +30 -0
  130. postwriter-0.1.0/src/postwriter/validation/soft/tension.py +23 -0
  131. postwriter-0.1.0/src/postwriter/validation/soft/thematic.py +29 -0
  132. postwriter-0.1.0/src/postwriter/validation/soft/transitions.py +33 -0
  133. postwriter-0.1.0/src/postwriter/validation/soft/voice_consistency.py +35 -0
@@ -0,0 +1,189 @@
1
+ Metadata-Version: 2.3
2
+ Name: postwriter
3
+ Version: 0.1.0
4
+ Summary: Orchestrated long-form fiction generation system
5
+ Author: Avram Score
6
+ Author-email: Avram Score <ascore@gmail.com>
7
+ Requires-Dist: sqlalchemy[asyncio]>=2.0,<3.0
8
+ Requires-Dist: asyncpg>=0.29,<1.0
9
+ Requires-Dist: alembic>=1.13,<2.0
10
+ Requires-Dist: redis>=5.0,<6.0
11
+ Requires-Dist: anthropic>=0.40,<1.0
12
+ Requires-Dist: pydantic>=2.0,<3.0
13
+ Requires-Dist: pydantic-settings>=2.0,<3.0
14
+ Requires-Dist: jinja2>=3.1,<4.0
15
+ Requires-Dist: rich>=13.0,<14.0
16
+ Requires-Dist: prompt-toolkit>=3.0,<4.0
17
+ Requires-Dist: networkx>=3.0,<4.0
18
+ Requires-Dist: click>=8.0,<9.0
19
+ Requires-Dist: ebooklib>=0.20
20
+ Requires-Python: >=3.12
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Postwriter
24
+
25
+ An orchestrated system for generating long-form fiction. Postwriter treats novel writing as a multi-pass engineering problem — planning at multiple narrative scales, drafting with stylistic variation, validating against explicit story state, and revising with manuscript-level awareness.
26
+
27
+ The target is not one-shot draft generation. The target is sustained narrative control, stylistic freshness, and cumulative artistic pressure across an entire manuscript.
28
+
29
+ ## What it does
30
+
31
+ Postwriter generates a complete novel (~80k words) through a pipeline of specialized AI agents:
32
+
33
+ 1. **Plan** — An architect agent designs the premise, structural spine, characters, style profile, chapters, and scenes. You approve the premise and act structure at human checkpoints.
34
+
35
+ 2. **Draft** — Each scene is drafted 3–5 times in parallel using distinct stylistic profiles (subtext-heavy, lyrical, compressed, etc.). Branches are not random paraphrases — they represent different rhetorical strategies.
36
+
37
+ 3. **Validate** — 5 hard validators (continuity, timeline, POV, knowledge state, banned patterns) gate acceptance. 10 soft critics score tension, emotion, prose vitality, voice consistency, dialogue, thematic integration, redundancy, transitions, scene purpose, and symbolic restraint.
38
+
39
+ 4. **Repair** — Failed drafts enter a targeted repair loop (up to 3 rounds). The repair planner prioritizes issues, and a local rewriter fixes specific problems while preserving what works.
40
+
41
+ 5. **Analyze** — Literary devices are detected across the manuscript (54 types, rule-based and model-based), tracked in temporal graphs, and evaluated for overuse, burstiness, imagery monoculture, and functional repetition.
42
+
43
+ 6. **Revise** — Manuscript-level audits check promises, character arcs, device ecology, rhythm, and thematic overstatement. A backward propagation engine modifies earlier scenes to strengthen later payoffs.
44
+
45
+ 7. **Export** — The final manuscript exports as markdown, with a full JSON canonical state dump and a generation report.
46
+
47
+ ## How it works
48
+
49
+ The manuscript is maintained as four linked representations:
50
+
51
+ - **Text layer** — the prose itself
52
+ - **Story-state layer** — facts, causality, character states, timeline, unresolved obligations
53
+ - **Stylistic layer** — voice targets, device preferences, imagery ecology, banned phrases
54
+ - **Analytical layer** — validator outputs, scores, device distributions, revision lineage
55
+
56
+ No important reasoning depends on prose alone when it can instead depend on structured state.
57
+
58
+ ## Quick start
59
+
60
+ ### Prerequisites
61
+
62
+ - Python 3.12+
63
+ - Docker (for PostgreSQL and Redis)
64
+ - An Anthropic API key
65
+
66
+ ### Install
67
+
68
+ ```bash
69
+ git clone https://github.com/avigold/postwriter.git
70
+ cd postwriter
71
+ uv sync
72
+ ```
73
+
74
+ ### Start infrastructure
75
+
76
+ ```bash
77
+ docker compose up -d
78
+ uv run alembic upgrade head
79
+ ```
80
+
81
+ ### Configure
82
+
83
+ ```bash
84
+ echo "PW_LLM_ANTHROPIC_API_KEY=sk-ant-your-key-here" > .env
85
+ ```
86
+
87
+ ### Run
88
+
89
+ ```bash
90
+ uv run postwriter new
91
+ ```
92
+
93
+ This walks you through an interactive bootstrap, then runs the full pipeline: plan → draft → revise → export.
94
+
95
+ ### Options
96
+
97
+ ```bash
98
+ # Use a generation profile
99
+ uv run postwriter new --profile fast_draft # Fewer branches, faster
100
+ uv run postwriter new --profile high_quality # More branches, more repair rounds
101
+ uv run postwriter new --profile budget_conscious # Minimize API costs
102
+
103
+ # Export a completed manuscript
104
+ uv run postwriter export <manuscript-id> --format all
105
+
106
+ # View manuscript dashboard
107
+ uv run postwriter dashboard <manuscript-id>
108
+
109
+ # List profiles
110
+ uv run postwriter profiles
111
+ ```
112
+
113
+ ## Context files
114
+
115
+ Drop markdown or image files into a `context/` directory to inform the writing. These are optional — if they're there, the system uses them; if not, it asks you everything interactively.
116
+
117
+ ```
118
+ context/
119
+ style-guide.md # Voice and prose preferences
120
+ characters.md # Character sketches
121
+ plot-outline.md # Story beats or synopsis
122
+ mood-board.png # Visual references
123
+ ```
124
+
125
+ Files can include YAML frontmatter to specify their type:
126
+
127
+ ```markdown
128
+ ---
129
+ type: style
130
+ ---
131
+ Write in short, declarative sentences. Avoid adverbs.
132
+ ```
133
+
134
+ Supported types: `sample_writing`, `plot`, `guidelines`, `characters`, `world`, `style`, `reference`. Without frontmatter, the system infers type from the filename.
135
+
136
+ Context files can be added at any time during generation. New files affect future scenes (forward-only — no retroactive re-evaluation).
137
+
138
+ ## Architecture
139
+
140
+ ```
141
+ postwriter/
142
+ agents/ # 10 specialized agents (architect, planner, writer, critics, rewriter)
143
+ canon/ # Canonical data store, context slicing, event logging
144
+ cli/ # Interactive CLI with rich terminal dashboards
145
+ context/ # User-provided reference file loader
146
+ db/ # Async PostgreSQL via SQLAlchemy 2.0, Alembic migrations
147
+ devices/ # Literary device detection (54 types), imagery classification
148
+ export/ # Markdown, JSON, and report exporters
149
+ graphs/ # Temporal device graphs and ecology metrics
150
+ llm/ # Anthropic SDK wrapper with Opus/Sonnet/Haiku tiering
151
+ models/ # 21 database tables covering the full canonical data model
152
+ orchestrator/ # Scene loop, branch management, global revision, checkpointing
153
+ prompts/ # Jinja2 prompt templates for all agent roles
154
+ repair/ # Repair planner and action specifications
155
+ revision/ # 5 manuscript-level audit passes + backward propagation
156
+ scoring/ # 11-dimension score vectors, Pareto comparison, device balance
157
+ validation/ # 5 hard validators + 10 soft critics
158
+ ```
159
+
160
+ ### Model tiering
161
+
162
+ | Tier | Role | Used for |
163
+ |------|------|----------|
164
+ | Opus | Creative judgment | Premise design, act structure, pivotal scene branches |
165
+ | Sonnet | Workhorse | Scene drafting, soft critics, repair, chapter/scene planning |
166
+ | Haiku | Mechanical checks | Hard validators, device detection, state extraction |
167
+
168
+ ### Key design principles
169
+
170
+ - Structured state outside prose — the database is the source of truth, not the text
171
+ - Narrow agents — each agent does one thing well
172
+ - Hard legality vs soft quality — separate concerns, separate priorities
173
+ - Repair locally before rewriting globally
174
+ - Branches represent rhetorical strategies, not random variation
175
+ - Graphs are advisory but consequential
176
+ - Human review at genuinely aesthetic decision points
177
+ - Plain prose is sometimes superior to ornamental variation
178
+
179
+ ## Tests
180
+
181
+ ```bash
182
+ uv run pytest tests/ -v
183
+ ```
184
+
185
+ 165 tests covering models, canon store, all validators and critics, device detection, graph metrics, scoring, repair planning, revision audits, export, and configuration profiles. Tests use a real PostgreSQL instance (Docker) for database tests and mock LLM responses for agent tests.
186
+
187
+ ## License
188
+
189
+ TBD
@@ -0,0 +1,167 @@
1
+ # Postwriter
2
+
3
+ An orchestrated system for generating long-form fiction. Postwriter treats novel writing as a multi-pass engineering problem — planning at multiple narrative scales, drafting with stylistic variation, validating against explicit story state, and revising with manuscript-level awareness.
4
+
5
+ The target is not one-shot draft generation. The target is sustained narrative control, stylistic freshness, and cumulative artistic pressure across an entire manuscript.
6
+
7
+ ## What it does
8
+
9
+ Postwriter generates a complete novel (~80k words) through a pipeline of specialized AI agents:
10
+
11
+ 1. **Plan** — An architect agent designs the premise, structural spine, characters, style profile, chapters, and scenes. You approve the premise and act structure at human checkpoints.
12
+
13
+ 2. **Draft** — Each scene is drafted 3–5 times in parallel using distinct stylistic profiles (subtext-heavy, lyrical, compressed, etc.). Branches are not random paraphrases — they represent different rhetorical strategies.
14
+
15
+ 3. **Validate** — 5 hard validators (continuity, timeline, POV, knowledge state, banned patterns) gate acceptance. 10 soft critics score tension, emotion, prose vitality, voice consistency, dialogue, thematic integration, redundancy, transitions, scene purpose, and symbolic restraint.
16
+
17
+ 4. **Repair** — Failed drafts enter a targeted repair loop (up to 3 rounds). The repair planner prioritizes issues, and a local rewriter fixes specific problems while preserving what works.
18
+
19
+ 5. **Analyze** — Literary devices are detected across the manuscript (54 types, rule-based and model-based), tracked in temporal graphs, and evaluated for overuse, burstiness, imagery monoculture, and functional repetition.
20
+
21
+ 6. **Revise** — Manuscript-level audits check promises, character arcs, device ecology, rhythm, and thematic overstatement. A backward propagation engine modifies earlier scenes to strengthen later payoffs.
22
+
23
+ 7. **Export** — The final manuscript exports as markdown, with a full JSON canonical state dump and a generation report.
24
+
25
+ ## How it works
26
+
27
+ The manuscript is maintained as four linked representations:
28
+
29
+ - **Text layer** — the prose itself
30
+ - **Story-state layer** — facts, causality, character states, timeline, unresolved obligations
31
+ - **Stylistic layer** — voice targets, device preferences, imagery ecology, banned phrases
32
+ - **Analytical layer** — validator outputs, scores, device distributions, revision lineage
33
+
34
+ No important reasoning depends on prose alone when it can instead depend on structured state.
35
+
36
+ ## Quick start
37
+
38
+ ### Prerequisites
39
+
40
+ - Python 3.12+
41
+ - Docker (for PostgreSQL and Redis)
42
+ - An Anthropic API key
43
+
44
+ ### Install
45
+
46
+ ```bash
47
+ git clone https://github.com/avigold/postwriter.git
48
+ cd postwriter
49
+ uv sync
50
+ ```
51
+
52
+ ### Start infrastructure
53
+
54
+ ```bash
55
+ docker compose up -d
56
+ uv run alembic upgrade head
57
+ ```
58
+
59
+ ### Configure
60
+
61
+ ```bash
62
+ echo "PW_LLM_ANTHROPIC_API_KEY=sk-ant-your-key-here" > .env
63
+ ```
64
+
65
+ ### Run
66
+
67
+ ```bash
68
+ uv run postwriter new
69
+ ```
70
+
71
+ This walks you through an interactive bootstrap, then runs the full pipeline: plan → draft → revise → export.
72
+
73
+ ### Options
74
+
75
+ ```bash
76
+ # Use a generation profile
77
+ uv run postwriter new --profile fast_draft # Fewer branches, faster
78
+ uv run postwriter new --profile high_quality # More branches, more repair rounds
79
+ uv run postwriter new --profile budget_conscious # Minimize API costs
80
+
81
+ # Export a completed manuscript
82
+ uv run postwriter export <manuscript-id> --format all
83
+
84
+ # View manuscript dashboard
85
+ uv run postwriter dashboard <manuscript-id>
86
+
87
+ # List profiles
88
+ uv run postwriter profiles
89
+ ```
90
+
91
+ ## Context files
92
+
93
+ Drop markdown or image files into a `context/` directory to inform the writing. These are optional — if they're there, the system uses them; if not, it asks you everything interactively.
94
+
95
+ ```
96
+ context/
97
+ style-guide.md # Voice and prose preferences
98
+ characters.md # Character sketches
99
+ plot-outline.md # Story beats or synopsis
100
+ mood-board.png # Visual references
101
+ ```
102
+
103
+ Files can include YAML frontmatter to specify their type:
104
+
105
+ ```markdown
106
+ ---
107
+ type: style
108
+ ---
109
+ Write in short, declarative sentences. Avoid adverbs.
110
+ ```
111
+
112
+ Supported types: `sample_writing`, `plot`, `guidelines`, `characters`, `world`, `style`, `reference`. Without frontmatter, the system infers type from the filename.
113
+
114
+ Context files can be added at any time during generation. New files affect future scenes (forward-only — no retroactive re-evaluation).
115
+
116
+ ## Architecture
117
+
118
+ ```
119
+ postwriter/
120
+ agents/ # 10 specialized agents (architect, planner, writer, critics, rewriter)
121
+ canon/ # Canonical data store, context slicing, event logging
122
+ cli/ # Interactive CLI with rich terminal dashboards
123
+ context/ # User-provided reference file loader
124
+ db/ # Async PostgreSQL via SQLAlchemy 2.0, Alembic migrations
125
+ devices/ # Literary device detection (54 types), imagery classification
126
+ export/ # Markdown, JSON, and report exporters
127
+ graphs/ # Temporal device graphs and ecology metrics
128
+ llm/ # Anthropic SDK wrapper with Opus/Sonnet/Haiku tiering
129
+ models/ # 21 database tables covering the full canonical data model
130
+ orchestrator/ # Scene loop, branch management, global revision, checkpointing
131
+ prompts/ # Jinja2 prompt templates for all agent roles
132
+ repair/ # Repair planner and action specifications
133
+ revision/ # 5 manuscript-level audit passes + backward propagation
134
+ scoring/ # 11-dimension score vectors, Pareto comparison, device balance
135
+ validation/ # 5 hard validators + 10 soft critics
136
+ ```
137
+
138
+ ### Model tiering
139
+
140
+ | Tier | Role | Used for |
141
+ |------|------|----------|
142
+ | Opus | Creative judgment | Premise design, act structure, pivotal scene branches |
143
+ | Sonnet | Workhorse | Scene drafting, soft critics, repair, chapter/scene planning |
144
+ | Haiku | Mechanical checks | Hard validators, device detection, state extraction |
145
+
146
+ ### Key design principles
147
+
148
+ - Structured state outside prose — the database is the source of truth, not the text
149
+ - Narrow agents — each agent does one thing well
150
+ - Hard legality vs soft quality — separate concerns, separate priorities
151
+ - Repair locally before rewriting globally
152
+ - Branches represent rhetorical strategies, not random variation
153
+ - Graphs are advisory but consequential
154
+ - Human review at genuinely aesthetic decision points
155
+ - Plain prose is sometimes superior to ornamental variation
156
+
157
+ ## Tests
158
+
159
+ ```bash
160
+ uv run pytest tests/ -v
161
+ ```
162
+
163
+ 165 tests covering models, canon store, all validators and critics, device detection, graph metrics, scoring, repair planning, revision audits, export, and configuration profiles. Tests use a real PostgreSQL instance (Docker) for database tests and mock LLM responses for agent tests.
164
+
165
+ ## License
166
+
167
+ TBD
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "postwriter"
3
+ version = "0.1.0"
4
+ description = "Orchestrated long-form fiction generation system"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Avram Score", email = "ascore@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "sqlalchemy[asyncio]>=2.0,<3.0",
12
+ "asyncpg>=0.29,<1.0",
13
+ "alembic>=1.13,<2.0",
14
+ "redis>=5.0,<6.0",
15
+ "anthropic>=0.40,<1.0",
16
+ "pydantic>=2.0,<3.0",
17
+ "pydantic-settings>=2.0,<3.0",
18
+ "jinja2>=3.1,<4.0",
19
+ "rich>=13.0,<14.0",
20
+ "prompt-toolkit>=3.0,<4.0",
21
+ "networkx>=3.0,<4.0",
22
+ "click>=8.0,<9.0",
23
+ "ebooklib>=0.20",
24
+ ]
25
+
26
+ [project.scripts]
27
+ postwriter = "postwriter.cli.app:main"
28
+
29
+ [build-system]
30
+ requires = ["uv_build>=0.10.7,<0.11.0"]
31
+ build-backend = "uv_build"
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "pytest>=8.0,<9.0",
36
+ "pytest-asyncio>=0.24,<1.0",
37
+ "pytest-cov>=5.0,<6.0",
38
+ "factory-boy>=3.3,<4.0",
39
+ "mypy>=1.10,<2.0",
40
+ "ruff>=0.6,<1.0",
41
+ ]
42
+
43
+ [tool.ruff]
44
+ target-version = "py312"
45
+ line-length = 100
46
+
47
+ [tool.ruff.lint]
48
+ select = ["E", "F", "I", "N", "UP", "B", "SIM", "TCH"]
49
+
50
+ [tool.pytest.ini_options]
51
+ asyncio_mode = "auto"
52
+ testpaths = ["tests"]
53
+
54
+ [tool.mypy]
55
+ python_version = "3.12"
56
+ strict = true
57
+ plugins = ["pydantic.mypy"]
@@ -0,0 +1,2 @@
1
+ def hello() -> str:
2
+ return "Hello from postwriter!"
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m postwriter`."""
2
+
3
+ from postwriter.cli.app import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,6 @@
1
+ """Agent implementations for the fiction orchestration system."""
2
+
3
+ from postwriter.agents.base import BaseAgent
4
+ from postwriter.agents.context import AgentContext, AgentResult
5
+
6
+ __all__ = ["BaseAgent", "AgentContext", "AgentResult"]
@@ -0,0 +1,118 @@
1
+ """Architect agents: premise design and structural spine."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from postwriter.agents.base import BaseAgent
10
+ from postwriter.types import AgentContext, ModelTier
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Response models
15
+ # ---------------------------------------------------------------------------
16
+
17
+
18
+ class PremiseResponse(BaseModel):
19
+ premise: str = Field(description="2-3 paragraph summary of the novel's core situation and trajectory")
20
+ controlling_design: dict[str, Any] = Field(
21
+ description="Architectural spine: central_question, arc_shape, turning_points, emotional_trajectory, plot_internal_relationship, tonal_range"
22
+ )
23
+ thematic_architecture: dict[str, Any] = Field(
24
+ description="How themes are embodied: character_tensions, symbolic_patterns, structural_embodiments"
25
+ )
26
+
27
+
28
+ class SpineResponse(BaseModel):
29
+ acts: list[dict[str, Any]] = Field(
30
+ description="List of acts, each with: name, ordinal, purpose, emotional_arc, chapter_count"
31
+ )
32
+ major_turning_points: list[dict[str, Any]] = Field(
33
+ description="Key structural turning points with: description, approximate_position, function"
34
+ )
35
+ arc_summary: str = Field(description="One-paragraph summary of the full arc shape")
36
+
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Agents
40
+ # ---------------------------------------------------------------------------
41
+
42
+
43
+ class PremiseArchitect(BaseAgent):
44
+ """Generates a novel premise and controlling design from a creative brief."""
45
+
46
+ role = "premise_architect"
47
+ model_tier = ModelTier.OPUS
48
+ template_name = "architect_premise.j2"
49
+ response_model = PremiseResponse
50
+
51
+ def build_template_context(self, context: AgentContext) -> dict[str, Any]:
52
+ brief = context.extra.get("creative_brief", {})
53
+ return {
54
+ "genre": brief.get("genre", ""),
55
+ "setting": brief.get("setting", ""),
56
+ "time_period": brief.get("time_period", ""),
57
+ "tone": brief.get("tone", ""),
58
+ "protagonist": brief.get("protagonist", ""),
59
+ "central_conflict": brief.get("central_conflict", ""),
60
+ "ending_direction": brief.get("ending_direction", ""),
61
+ "themes": brief.get("themes", []),
62
+ "constraints": brief.get("constraints", []),
63
+ "user_context": context.user_context,
64
+ }
65
+
66
+ def build_system_prompt(self, context: AgentContext) -> str:
67
+ base = (
68
+ "You are a master fiction architect with deep knowledge of narrative structure, "
69
+ "literary tradition, and genre conventions. Your task is to design a novel premise "
70
+ "and controlling design that will sustain a full-length manuscript.\n\n"
71
+ "Design for emotional credibility, thematic depth, and structural integrity. "
72
+ "Themes should be embodied through character and situation, never stated directly."
73
+ )
74
+ # Append user context if available
75
+ if context.user_context:
76
+ base += "\n\n## User-Provided Reference Materials\n\n"
77
+ for ctx in context.user_context:
78
+ base += f"### {ctx.get('name', 'Reference')} ({ctx.get('type', 'unknown')})\n"
79
+ base += ctx.get("content", "") + "\n\n"
80
+ return base
81
+
82
+ def _max_tokens(self) -> int:
83
+ return 8192
84
+
85
+ def _temperature(self) -> float:
86
+ return 1.0
87
+
88
+
89
+ class SpineArchitect(BaseAgent):
90
+ """Designs the structural spine (acts and major movements) from a premise."""
91
+
92
+ role = "spine_architect"
93
+ model_tier = ModelTier.OPUS
94
+ template_name = "architect_spine.j2"
95
+ response_model = SpineResponse
96
+
97
+ def build_template_context(self, context: AgentContext) -> dict[str, Any]:
98
+ return {
99
+ "premise": context.premise,
100
+ "controlling_design": context.controlling_design,
101
+ "target_word_count": context.extra.get("target_word_count", 80000),
102
+ "target_chapters": context.extra.get("target_chapters", "30-40"),
103
+ "user_context": context.user_context,
104
+ }
105
+
106
+ def build_system_prompt(self, context: AgentContext) -> str:
107
+ return (
108
+ "You are a master fiction architect. Given a premise and controlling design, "
109
+ "design the structural spine of the novel: its acts, major movements, and "
110
+ "turning points.\n\n"
111
+ "Each act should have a clear purpose and emotional trajectory. "
112
+ "Distribute tension, revelation, and release across the full arc. "
113
+ "The spine should support the controlling design without over-determining "
114
+ "individual scenes."
115
+ )
116
+
117
+ def _max_tokens(self) -> int:
118
+ return 8192