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.
- postwriter-0.1.0/PKG-INFO +189 -0
- postwriter-0.1.0/README.md +167 -0
- postwriter-0.1.0/pyproject.toml +57 -0
- postwriter-0.1.0/src/postwriter/__init__.py +2 -0
- postwriter-0.1.0/src/postwriter/__main__.py +6 -0
- postwriter-0.1.0/src/postwriter/agents/__init__.py +6 -0
- postwriter-0.1.0/src/postwriter/agents/architect.py +118 -0
- postwriter-0.1.0/src/postwriter/agents/base.py +278 -0
- postwriter-0.1.0/src/postwriter/agents/branch_profiles.py +167 -0
- postwriter-0.1.0/src/postwriter/agents/chapter_planner.py +78 -0
- postwriter-0.1.0/src/postwriter/agents/character_designer.py +86 -0
- postwriter-0.1.0/src/postwriter/agents/context.py +9 -0
- postwriter-0.1.0/src/postwriter/agents/local_rewriter.py +62 -0
- postwriter-0.1.0/src/postwriter/agents/promise_seeder.py +63 -0
- postwriter-0.1.0/src/postwriter/agents/scene_planner.py +91 -0
- postwriter-0.1.0/src/postwriter/agents/scene_writer.py +80 -0
- postwriter-0.1.0/src/postwriter/agents/style_builder.py +84 -0
- postwriter-0.1.0/src/postwriter/canon/__init__.py +7 -0
- postwriter-0.1.0/src/postwriter/canon/events.py +62 -0
- postwriter-0.1.0/src/postwriter/canon/mutations.py +65 -0
- postwriter-0.1.0/src/postwriter/canon/slicer.py +263 -0
- postwriter-0.1.0/src/postwriter/canon/store.py +316 -0
- postwriter-0.1.0/src/postwriter/cli/__init__.py +1 -0
- postwriter-0.1.0/src/postwriter/cli/app.py +391 -0
- postwriter-0.1.0/src/postwriter/cli/bootstrap.py +140 -0
- postwriter-0.1.0/src/postwriter/cli/dashboards/__init__.py +1 -0
- postwriter-0.1.0/src/postwriter/cli/dashboards/manuscript.py +102 -0
- postwriter-0.1.0/src/postwriter/cli/dashboards/scene.py +103 -0
- postwriter-0.1.0/src/postwriter/cli/display.py +210 -0
- postwriter-0.1.0/src/postwriter/cli/execution.py +35 -0
- postwriter-0.1.0/src/postwriter/config.py +69 -0
- postwriter-0.1.0/src/postwriter/context/__init__.py +5 -0
- postwriter-0.1.0/src/postwriter/context/condenser.py +166 -0
- postwriter-0.1.0/src/postwriter/context/loader.py +320 -0
- postwriter-0.1.0/src/postwriter/db/__init__.py +5 -0
- postwriter-0.1.0/src/postwriter/db/migrations/README +1 -0
- postwriter-0.1.0/src/postwriter/db/migrations/env.py +58 -0
- postwriter-0.1.0/src/postwriter/db/migrations/script.py.mako +28 -0
- postwriter-0.1.0/src/postwriter/db/migrations/versions/a68fbb4eb8ce_initial_schema.py +412 -0
- postwriter-0.1.0/src/postwriter/db/session.py +46 -0
- postwriter-0.1.0/src/postwriter/devices/__init__.py +6 -0
- postwriter-0.1.0/src/postwriter/devices/annotation.py +149 -0
- postwriter-0.1.0/src/postwriter/devices/detectors/__init__.py +6 -0
- postwriter-0.1.0/src/postwriter/devices/detectors/figurative.py +89 -0
- postwriter-0.1.0/src/postwriter/devices/detectors/lexical.py +219 -0
- postwriter-0.1.0/src/postwriter/devices/detectors/narrative.py +106 -0
- postwriter-0.1.0/src/postwriter/devices/detectors/rhythm.py +151 -0
- postwriter-0.1.0/src/postwriter/devices/imagery_domains.py +137 -0
- postwriter-0.1.0/src/postwriter/devices/overuse_rules.py +143 -0
- postwriter-0.1.0/src/postwriter/devices/taxonomy.py +74 -0
- postwriter-0.1.0/src/postwriter/errors.py +67 -0
- postwriter-0.1.0/src/postwriter/export/__init__.py +7 -0
- postwriter-0.1.0/src/postwriter/export/epub.py +256 -0
- postwriter-0.1.0/src/postwriter/export/json_export.py +125 -0
- postwriter-0.1.0/src/postwriter/export/markdown.py +57 -0
- postwriter-0.1.0/src/postwriter/export/report.py +147 -0
- postwriter-0.1.0/src/postwriter/graphs/__init__.py +6 -0
- postwriter-0.1.0/src/postwriter/graphs/metrics.py +166 -0
- postwriter-0.1.0/src/postwriter/graphs/temporal.py +99 -0
- postwriter-0.1.0/src/postwriter/llm/__init__.py +6 -0
- postwriter-0.1.0/src/postwriter/llm/budget.py +61 -0
- postwriter-0.1.0/src/postwriter/llm/client.py +208 -0
- postwriter-0.1.0/src/postwriter/logging_config.py +108 -0
- postwriter-0.1.0/src/postwriter/models/__init__.py +57 -0
- postwriter-0.1.0/src/postwriter/models/analytics.py +129 -0
- postwriter-0.1.0/src/postwriter/models/base.py +36 -0
- postwriter-0.1.0/src/postwriter/models/characters.py +87 -0
- postwriter-0.1.0/src/postwriter/models/core.py +166 -0
- postwriter-0.1.0/src/postwriter/models/events.py +35 -0
- postwriter-0.1.0/src/postwriter/models/narrative.py +141 -0
- postwriter-0.1.0/src/postwriter/models/style.py +51 -0
- postwriter-0.1.0/src/postwriter/orchestrator/__init__.py +1 -0
- postwriter-0.1.0/src/postwriter/orchestrator/branch_manager.py +110 -0
- postwriter-0.1.0/src/postwriter/orchestrator/checkpoint.py +117 -0
- postwriter-0.1.0/src/postwriter/orchestrator/critic_runner.py +72 -0
- postwriter-0.1.0/src/postwriter/orchestrator/engine.py +152 -0
- postwriter-0.1.0/src/postwriter/orchestrator/error_handling.py +71 -0
- postwriter-0.1.0/src/postwriter/orchestrator/global_revision.py +293 -0
- postwriter-0.1.0/src/postwriter/orchestrator/planner.py +530 -0
- postwriter-0.1.0/src/postwriter/orchestrator/policies.py +32 -0
- postwriter-0.1.0/src/postwriter/orchestrator/scene_loop.py +278 -0
- postwriter-0.1.0/src/postwriter/profiles.py +80 -0
- postwriter-0.1.0/src/postwriter/project.py +74 -0
- postwriter-0.1.0/src/postwriter/prompts/__init__.py +5 -0
- postwriter-0.1.0/src/postwriter/prompts/loader.py +41 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/__init__.py +0 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/architect_premise.j2 +70 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/architect_spine.j2 +44 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/chapter_planner.j2 +60 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/character_designer.j2 +54 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/local_rewriter.j2 +36 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/promise_seeder.j2 +37 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/scene_planner.j2 +77 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/scene_writer.j2 +70 -0
- postwriter-0.1.0/src/postwriter/prompts/templates/style_builder.j2 +49 -0
- postwriter-0.1.0/src/postwriter/py.typed +0 -0
- postwriter-0.1.0/src/postwriter/repair/__init__.py +6 -0
- postwriter-0.1.0/src/postwriter/repair/actions.py +22 -0
- postwriter-0.1.0/src/postwriter/repair/planner.py +178 -0
- postwriter-0.1.0/src/postwriter/revision/__init__.py +5 -0
- postwriter-0.1.0/src/postwriter/revision/arc_audit.py +141 -0
- postwriter-0.1.0/src/postwriter/revision/backward_propagation.py +259 -0
- postwriter-0.1.0/src/postwriter/revision/base.py +29 -0
- postwriter-0.1.0/src/postwriter/revision/device_ecology.py +93 -0
- postwriter-0.1.0/src/postwriter/revision/promise_audit.py +68 -0
- postwriter-0.1.0/src/postwriter/revision/rhythm_audit.py +85 -0
- postwriter-0.1.0/src/postwriter/revision/theme_overstatement.py +139 -0
- postwriter-0.1.0/src/postwriter/scoring/__init__.py +5 -0
- postwriter-0.1.0/src/postwriter/scoring/comparison.py +100 -0
- postwriter-0.1.0/src/postwriter/scoring/device_balance.py +28 -0
- postwriter-0.1.0/src/postwriter/scoring/thresholds.py +62 -0
- postwriter-0.1.0/src/postwriter/scoring/vectors.py +110 -0
- postwriter-0.1.0/src/postwriter/types.py +313 -0
- postwriter-0.1.0/src/postwriter/validation/__init__.py +9 -0
- postwriter-0.1.0/src/postwriter/validation/base.py +135 -0
- postwriter-0.1.0/src/postwriter/validation/hard/__init__.py +16 -0
- postwriter-0.1.0/src/postwriter/validation/hard/banned_patterns.py +92 -0
- postwriter-0.1.0/src/postwriter/validation/hard/continuity.py +110 -0
- postwriter-0.1.0/src/postwriter/validation/hard/knowledge.py +84 -0
- postwriter-0.1.0/src/postwriter/validation/hard/pov.py +74 -0
- postwriter-0.1.0/src/postwriter/validation/hard/timeline.py +75 -0
- postwriter-0.1.0/src/postwriter/validation/soft/__init__.py +25 -0
- postwriter-0.1.0/src/postwriter/validation/soft/base.py +111 -0
- postwriter-0.1.0/src/postwriter/validation/soft/dialogue.py +25 -0
- postwriter-0.1.0/src/postwriter/validation/soft/emotion.py +23 -0
- postwriter-0.1.0/src/postwriter/validation/soft/prose_vitality.py +38 -0
- postwriter-0.1.0/src/postwriter/validation/soft/redundancy.py +30 -0
- postwriter-0.1.0/src/postwriter/validation/soft/scene_purpose.py +23 -0
- postwriter-0.1.0/src/postwriter/validation/soft/symbolic_restraint.py +30 -0
- postwriter-0.1.0/src/postwriter/validation/soft/tension.py +23 -0
- postwriter-0.1.0/src/postwriter/validation/soft/thematic.py +29 -0
- postwriter-0.1.0/src/postwriter/validation/soft/transitions.py +33 -0
- 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,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
|