Skip to content

Changelog

This is a curated summary. The authoritative, full-detail log is CHANGELOG.md in the repo. The format follows Keep a Changelog and the project adheres to Semantic Versioning.

  • Repository restructure: clean root, one home per thing. Spec docs moved under docs/ (plan.md, design.md, prd.md, learning.md, roadmap.md, proposals/); the Gradio Hugging Face Space demo renamed web/demo/ (no longer confusable with the package’s webui/); SampleRun/ moved to examples/sample-run/ (the bulky manuscript.pdf dropped - the markdown output shows the same run); shipped data single-sourced under src/writingagent/resources/ (gold/, personas/, seeds/, models.yaml). The repo root now holds only standard community files + pyproject.toml.
  • BREAKING - runtime state moved out of the repo to the agent home. brain/, .index/, config/models.yaml + config/settings.yaml, and the /setkey .env now live under the agent home: $WRITINGAGENT_HOME if set, else the OS user-data dir (%LOCALAPPDATA%\writingagent on Windows, ~/Library/Application Support/writingagent on macOS, $XDG_DATA_HOME/writingagent on Linux). Running the tool from any directory no longer scatters state there, and pip installs stop writing config next to site-packages. To migrate an old checkout, move those files/folders from the repo checkout root into the agent home.
  • BREAKING - the repo-root launcher writingagent.py was removed. Use the installed writing-agent command or python -m writingagent.
  • Web dashboard accessibility + polish pass. Keyboard access for the primary surfaces (sidebar nav is real buttons; project rows and approach cards are focusable and Enter/Space-activatable; settings inputs gained programmatic labels; toasts announce via live regions; the run log is a role="log"). Hash routing (#projects, #project/<id>): refresh and back/forward keep your place, and the tab title follows the view (per-view titles). The primary button’s label color is a themed token with a contrast guard in applyWebTheme(), so a named palette can no longer produce an unreadable button. A finished run now shows an “Open the piece” completion banner instead of only a log line. Also: the Projects poll repaints only when data changes (no more 6s flicker), the banner side-stripe was dropped (tint + icon carry the state), and the project header rows align to the centered content column.
  • Shipped defaults bundled in the wheel. Pip installs used to get no config/models.yaml (so no per-node routing and no fallback model) and no seed skills (seed-skills installed 0). The package now bundles both: models.yaml is copied out on first run into a real, editable config/models.yaml, and the 13 built-in craft skills seed from the bundled copies. Sync tests pin the bundled files byte-identical to the repo originals.
  • Model-agnostic messaging + docs de-slop. The README and human-authored docs now lead with “any OpenAI-compatible host, no blessed default” rather than an OpenRouter/DeepSeek default, and the project’s own anti-slop rule (no em-dashes) was applied to the prose and the banner/architecture SVGs.
  • Refactor + CI maintenance. The near-identical book/article run-op bodies were de-duplicated (behavior-preserving); GitHub Actions bumped to current majors; ruff check . is green repo-wide.
  • Repo-wide docs consistency sweep, audited against the code. Command snippets, the budget-mode default, the OpenRouter-only wording, the trace-policy data floor (≥10 per arm), the diagram node’s 4k spec cap, and SECURITY.md’s /set/outbound claims now all match the implementation.
  • The npm launcher. Removed entirely - it was never published or maintained. The Python install (pip install writing-agent, pipx, or source) is the only supported path.
  • 23 model hosts, no blessed default. First-party Anthropic, Perplexity, Cerebras, and SambaNova, plus AWS Bedrock and Azure OpenAI via an OpenAI-compatible gateway - 23 hosts total, one wire format (providers.py). There is no default host: a first-run wizard detects a key already in the environment and offers it, or lets you choose a provider and paste its key; switch anytime with /provider. New /setkey [<key>] saves the active provider’s key to .env and applies it live.
  • PyPI release + open-source hardening. Release automation (Trusted-Publishing/OIDC on a v* tag), CI coverage (Codecov) and a gitleaks secret-scan, Dependabot, README PyPI / Docs / Python / Platforms / License badges, CODEOWNERS, CITATION.cff, FUNDING.yml, and ROADMAP.md.
  • Zero-install Gradio web demo (web/app.py, pip install -e ".[web]"). A browser front-end over the public Agent/Project facade: a free preview runs the whole flow offline (fake mode, $0, no key), or paste your own provider key for a real piece. Ships a Hugging Face Space config; the package imports gradio lazily. (Distinct from the local writing-agent web dashboard.)
  • Local web dashboard (writing-agent web). A browser UI over the same engine the TUI drives - pure-stdlib ThreadingHTTPServer + Server-Sent Events + a single-page app in src/writingagent/webui/. writing-agent web [--port N] [--no-browser] (default port 8787, auto-opens the browser). Binds 127.0.0.1 only, no auth (same trust boundary as the TUI), and runs one background job at a time. Views: Studio, Live run (SSE log + run controls), Projects, Project (Overview / Activity / Evals / Artifacts / Rejected / Export / Cost), Telemetry, Skills, Settings. Export offers every format (pdf/epub/html/docx/txt/md) plus a Rewrite (restyle) action; theme picker is System / Light / Dark + the named palettes. Follows the editorial design system (design.md).
  • SEO layer (seo.py, writing-agent seo). A deterministic on-page audit (title/meta lengths, keyword placement + density, heading hierarchy, word-count floor, reading grade, outbound-link and image-alt hygiene) plus a one-call flash-tier keyword pack (primary/secondary keywords, meta description, per-platform hashtags). Runs automatically as part of a finished write (gated by auto_promote); the target keyword is threaded into the writer up front (seo_keyword) and the H1 title is optimized after validation. Writes seo_report.md + keywords.json. It only edits the article and names keywords/hashtags - it never posts or schedules anything anywhere. Pin a keyword with writing-agent seo --keyword "…".
  • Promote / repurpose / restyle (promote.py, writing-agent promote). Platform-native variants of a finished piece - x-thread, linkedin, newsletter-teaser, tldr (each one flash call) - plus 5 headline variants and a voice restyle (register / persona / emotion), all reusing the SEO keyword pack. writing-agent promote --to x-thread,linkedin selects formats; runs automatically after a finished write when auto_promote is on. Generates LOCAL promo/*.md artifacts only - nothing is posted or scheduled anywhere.
  • Cost - budget mode (cost_mode). The recommended opt-in cost_mode: budget (the shipped default is standard; enable with /set cost_mode budget) applies a per-unit token budget (budget_tokens_per_unit, default 20000) plus a fixed overhead so a full piece finishes instead of pausing, and routes the judgment nodes (critic / judge / verifier / consolidation / diagram) to the cheaper flash tier. An explicit max_run_tokens (> 0) is a hard ceiling that pauses the run when hit and always wins over the auto-scaled budget. Per-node and per-unit telemetry attribution powers the Telemetry / Cost views.
  • Figure reconciliation + Rejected review. Generated diagrams are deterministically embedded into the manuscript; unused images and dropped draft variants are logged and reviewable in the web Rejected tab (rejected.jsonl + versions/).
  • Firecrawl search provider. search_provider: firecrawl (needs FIRECRAWL_API_KEY in .env) as an alternative to the default duckduckgo; a missing key degrades back to DuckDuckGo. verify_excerpt_chars (default 6000) sets the claim verifier’s full-text excerpt size.
  • Editorial design system (design.md v2). A portable, cross-domain design system - ink on warm paper, one accent = manuscript red #a3341f, Fraunces serif for display + reading, the pilcrow brand mark, the Caret loader, flat/borderless, square content surfaces, WCAG-AA verified. Governs both the web dashboard and the TUI (default editorial skin - “ink & brass”: gold primary + brass + manuscript-red status). Named themes recolor, never restructure. Spec lives at the repo root design.md.
  • Write in any field - the craft engine + compositor. A register-parameterized craft engine (11 genre registers, few-shot exemplars, a shipped gold style corpus, and genre-aware deterministic metrics) plus a compositor that layers a manner and an emotion over the register: 46 personas (18 archetypes + 28 public-domain manners - e.g. shakespearean, austen-ironic, wildean, dickensian, whitmanesque, twain-vernacular; no living authors, original-pastiche exemplars) and 12 emotions with anti-cliché deny-lists wired into the detector. The cascade register ⊃ field ⊃ persona ⊃ emotion ⊃ skills selects one voice and logs why, never stacks. New settings: register, field, citation_style, craft_passes, persona, emotion. (plan §22–23.)
  • Self-directing mode - the agentic controller (opt-in, off by default). Instead of the fixed order, a policy chooses each unit’s next move (draft / gather research / read canon) and the writer can call research/canon tools mid-draft; bounded by per-unit step + token caps, with the fixed pipeline as the always-on fallback (off = byte-identical). A policy learned from your own run traces (agentic_policy: trace) improves the choices and abstains until it has ≥10 labelled units per arm. New /agentic and /trace commands surface the mode and the learned verdict; settings agentic, agentic_policy, agentic_controller_model, agentic_max_unit_steps, agentic_inline_tools, … (plan §21.)
  • Learning loop v2 - ablation duels (skill_duels, opt-in). On a unit with an undecided skill, an extra draft is written with that skill held out and the critic compares it to the full-skill draft - a true cause-and-effect test of the skill’s lift. reconcile prefers a smoothed, sample-gated duel win-rate over the first-pass fallback, and /skills shows it. Adds skill_distill (deterministic, non-destructive retirement of near-duplicate skills; off) and watch_blocking (watch-list blocks only clear/concrete violations; false = advisory).
  • learning.md - a layman’s, chronological guided tour of the whole codebase (folders, files, the studio-of-specialists model, the brain-on-disk design, model routing, and the why behind each).
  • Colourblind-safe highcontrast theme (Okabe-Ito; ok = blue, error = vermillion - never a red/green pair). 11 themes total.
  • Whole-run ETA on the live dashboard (~Nm left, from this session’s average time-per-unit).
  • First-run onboarding. With no API key set, the welcome shows how to set the key or try the whole flow free with WRITINGAGENT_FAKE=1 - instead of suggesting a command that would fail.
  • Evidence report (shareable proof). Every article ships an evidence_report.md - the thesis it argues + every source ranked by influence (0–100), built from the finished piece with no model call. Auto-generated at assembly, refreshed by polish, regenerated on demand by the new evidence command (Project.evidence_report()).
  • Clean references, citations & figures (deterministic polish). A pure-Python pass (no LLM, ~0 tokens) builds one end ## References list ranked by influence (rank_references), strips inline [N] markers from the body after scoring (strip_inline_citations), pulls out stray reference dumps, and de-duplicates figures. The new polish command re-applies the whole pass to an already-finished manuscript and re-exports - no model call.
  • TUI UX overhaul. No command dead-ends (a reserved word without its slash runs the command; \ forces chat); a normalized trust chip (✓ approved · insight 5/5 · confidence ●●●○○); live run controls (esc/p pauses cleanly at the next unit boundary, m drops to manual); a structured paused card and recover-from-here export errors; accessibility (WRITINGAGENT_A11Y line-mode, WRITINGAGENT_REDUCED_MOTION) + a narrow-terminal wordmark; a proactive no-key warning; and progressive /help <topic>.
  • Quality machinery II - independence, verification, compounding. A side-by-side tournament judge picks the best of N divergent drafts (replacing scalar self-score selection); claim↔source verification makes unsupported cited claims blocking under deep research; the writer engages the thesis counterargument; a closed table-read loop (table_read_revise) applies the reader’s top fix as one bounded revision; the learner distils skills from the model’s own preference data (tournament + revision outcomes), candidate-only. New judge/verifier model slugs and tournament_judge / verify_claims / table_read_revise settings, surfaced in /model, /features, and the chat context.
  • Diagrams rebuilt - structured spec → deterministic layout. The model now authors a DiagramSpec; a layout engine measures text and places everything on a grid, so labels never overflow or collide (back edges are detected so a feedback arrow can’t reverse a pipeline; arrowheads are explicit, PDF-safe). An optional D2 + ELK backend (diagram_engine) handles complex graphs, with an injected legend; the zero-dependency built-in engine is the default and fallback.
  • Shipped defaults flipped to match common usage - new projects default to mode: article, autonomous: true (never pause for review), and use_images: true.
  • Public Python API - a stable Agent + Project facade (create, run, resume, revise, evaluate, export) plus a one-shot write(), re-exported from the package root.
  • Quality machinery (originality over slop-absence) - a per-article thesis (contestable claim + steelmanned counterargument) injected and enforced by the critic; voice exemplars (voice/, fed by /praise); divergent first drafts (best-of-N at varied temperatures); an insight score (1–5) with a min_insight gate plus clarity/structure/evidence scores and deterministic style metrics; a surgical humanizer that rewrites only flagged sentences.
  • Trust machinery - version snapshots (<project>/versions/) with versions and read --v K; revise --chapter N to rewrite one committed unit with a semantic + text diff; brief (goal panel); tableread [--as "persona"]; eval (judged 5-dimension rubric + deterministic metrics → eval_report.md).
  • Interactive TUI - escalation picker on a stalled unit, manual divergent-variant picking, an outline+thesis approval gate after new, a post-run summary card + terminal bell, and a draft-opening glimpse in the dashboard.
  • write command - one-shot autonomous flow: an upfront LLM-generated interview, then a fully autonomous run to a finished, exported file.
  • Theme system - 10 TUI themes, each with its own palette, wordmark figlet face, and glyphs; switch live with /theme. Accent-distinctness and font availability are test-enforced.
  • Deep multi-source researcher (deep_research) - query expansion, concurrent search, full page-text fetch (Scrapo or stdlib), cross-source synthesis with numbered citations.
  • Export overhaul - PDF code/diagram wrapping, Mermaid → PNG via mermaid.ink (cached), EPUB image items, glyph normalization, placeholder cleanup, reference renumbering.
  • Run cost kill-switch (max_run_tokens), structured telemetry (per-call JSONL + /dashboard), prompt-injection defense (data-only fencing), and a fetch safety gate (SSRF guard + robots.txt + per-host rate-limiting).
  • Animated run dashboard, compact welcome screen + /features, run-mode toggle (/auto, run --autonomous/--manual), autocomplete + persistent history, and a --plain / NO_COLOR mode.
  • Open-source scaffolding: MIT LICENSE, CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md, issue/PR templates, GitHub Actions CI (Linux/macOS/Windows × Python 3.10–3.13), ruff, and pre-commit hooks.
  • Natural language can run any slash command, including /set. The chat assistant’s system prompt documents the full slash surface with NL triggers, and /set was removed from the chat denylist so config requests (“turn on researcher”, “use the poe-gothic persona”) execute from plain English. Config is reversible and each executed line is echoed; only /user and delete stay manual.
  • Internals reorganised into packages (behavior-preserving). The two largest modules were split behind stable facades, so every orchestrator.X / shell.X import is unchanged: orchestrator/ (common · book · article · export · manage · review) and shell/ (branding · help · commands · dashboard · chat · dispatch · slash · session · repl). Preceded by a book↔article de-duplication pass; no file now exceeds ~1k lines.
  • Friendlier, recoverable errors - bad/missing API key, rate-limit, network blip, and locked export files now show a clear next step (ui.explain_error) instead of a raw RuntimeError: ….
  • /features lists the new toggles (skill_duels, skill_distill, watch_blocking); live-run controls wording clarified - all interrupts are resumable (/delete discards).
  • Diagrams: structured spec → deterministic renderer. The model no longer emits SVG (it can’t measure text, so labels overflowed); it now returns a structured DiagramSpec and a pure-Python layout engine (diagram.py) measures text, sizes boxes to fit, places nodes on a grid, routes orthogonal edges, and draws explicit, PDF-safe arrowheads (back edges detected so a feedback arrow can’t reverse a pipeline).
  • Token / cost-efficiency pass (telemetry-grounded, quality unchanged): cache-hit telemetry in the usage summary + JSONL; a lossless JSON-Schema shrink on structured calls; a thesis brief (claim+arguments only) to the critic/judge; per-node max_tokens; and an opt-in divergent_skeletons (draft variants short, expand only the winner - ~60% fewer discarded-draft tokens). (Cost work has since moved to the cost_mode: budget profile - see Added.)
  • Actually claim the DeepSeek prompt cache. The new openrouter_providers setting pins the OpenRouter upstream order (default unpinned/""; set it to e.g. DeepSeek to opt in) so the prompt cache hits - ~80% of the prefix at ~3.5× lower cost vs. default load-balancing; provider=deepseek direct guarantees it.
  • Diagrams: diagram_engine: auto now defaults to the built-in engine. It measures text and lays figures out compactly; D2 + ELK is explicit opt-in (diagram_engine: d2) since it tends to render wide. The zero-dependency built-in engine stays default and fallback.
  • use_researcher now defaults on - citations are unverifiable otherwise.
  • Critic routed to deepseek-v4-pro - insight scoring and thesis checks need the pro tier. Writer temperature set explicit (0.9); humanizer dropped to 0.3.
  • PDF page size A5 → A4 so code fits.
  • Hardened LLM calls: classified retry with backoff, fail-fast on 4xx, request timeout, and a structured-output repair retry.
  • pyproject.toml is now the canonical dependency source.
  • Review-driven fix sweep (2026-07-16). A full-codebase review (redundancy · mismatches · optimization) drove one batch of confirmed-defect fixes across every subsystem - escalation score-array desync, N-style citation preservation, register-aware surgical guards, max_context_chars: 0 honored, chapter-writer length-truncation recovery, learned-policy corpus no longer skewed by revise, the agentic critique panel wired for books, SQLite connection leak on a corrupt db, web-dashboard raster images, whole-word emotion/SEO matching, plus dead-code removal. Suite green (523 passed / 1 skipped), ruff clean.
  • Structured-output truncation on the reasoning tier. A reasoning model (deepseek-v4-pro) can spend its whole budget thinking and return empty / cut off with finish_reason=length; the old retry re-sent the same too-small budget before degrading to the flash fallback. complete_structured now raises max_tokens and retries the same model, and models.yaml ships a max_tokens floor for the judgment nodes (critic/judge/verifier = 8000). Validated on the first real OpenRouter run (the DeepSeek prompt cache also confirmed engaging at 36% of prompt tokens).
  • autonomous: true was silently ignored by new (a store_true default shadowed it); --autonomous is now tri-state with a --no-autonomous override.
  • Post-completion revise now critiques with pipeline-parity context (watch-list, intake requirements, prior-unit context, length target).
  • CI install failure on Ubuntu (svglib 1.6 → pycairo) fixed by pinning svglib<1.6.
  • PDF exports with no images (default, no cairosvg) now render SVG diagrams as vector art via svglib; Wikimedia image search v1/v2 shape mismatch fixed; long chat replies no longer duplicate in scrollback; read --manuscript resolves article paths; the test suite no longer pollutes real telemetry.
  • The conversational assistant auto-runs commands - including /set, so plain-English config changes take effect (reversible, each executed line echoed back). Only delete (irreversible) and /user (switches identity) stay fenced - the human must type them. Ids are validated and deletes are confined to the brain dir.
  • Exported HTML is sanitized (script/iframe/event handlers stripped).
  • Deep-research fetches are SSRF-guarded, robots.txt-respecting, and per-host rate-limited.
  • Context-compression “headroom” (use_headroom) removed entirely. It saved ~nothing on single-turn payloads and hurt the DeepSeek prompt-cache hit rate by perturbing the cacheable prefix. The setting, the [headroom] extra, and the llm.py code path are all gone; prompt-cache pinning (openrouter_providers) plus cost_mode: budget are the cost story now.
  • Dead vertical-slice prototype (run.py, src/writingagent/slice.py).
  • Initial book + article pipelines: plan → write → critique → revise → humanise → commit, with canon/consolidation, learned craft skills, and six export formats.