Skip to content

Programmatic use

WRITING AGENT is a TUI-first tool, but every project command also runs one-shot from your shell - which is all you need to automate it from a script or CI today.

Every command works without the interactive shell as writing-agent <command> ... (the console script from pip install, or python writingagent.py <command> ... from a source checkout):

Terminal window
writing-agent new --abstract "The psychology of decision fatigue" --pick 1
writing-agent run
writing-agent export --format pdf
writing-agent read --manuscript

The one-shot write command does the whole thing - interview, autonomous run, export - in a single invocation:

Terminal window
python writingagent.py write --abstract "..." --chapters 6 --max-revisions 2

Flags map to the same settings you’d change with /set. See the full Commands reference.

writing-agent web [--port N] [--no-browser] starts a local dashboard (default port 8787, auto-opens the browser) - a pure-stdlib ThreadingHTTPServer + Server-Sent Events over the same engine the TUI drives, with a single-page app served from src/writingagent/webui/static/.

It’s a small JSON API you can script against. All views read the on-disk brain (the same run_state, telemetry JSONL and agent_trace.jsonl the TUI uses), so the two surfaces can’t drift.

RouteReturns
/ · /index.html · /static/*the single-page app + whitelisted assets
/api/stateuser, theme + theme palettes, dropdown options, settings, model routing, project list, active job
/api/project?project=<id>one project’s status, run state, artifact list, whether a trace exists
/api/artifact?project=<id>&name=<name>one whitelisted artifact’s text (manuscript, seo, outline, promo/*, versions/*, …)
/api/trace?project=<id>the agentic decision trace records
/api/rejected?project=<id>dropped items (rejected.jsonl), draft-variant snapshots (versions/), and unused images
/api/evals?project=<id>scores, insights, per-attempt eval JSON, and eval_report.md
/api/telemetry?project=<id>&run_id=<id>per-call telemetry summary + recent records (per-node / per-unit cost attribution)
/api/skillsthe skill index, skill files, and the watch-list
/api/events?job=<id>SSE stream: replays a job’s buffered events, then streams live log/state/done events

All request/response bodies are JSON.

RouteBody → effect
/api/plan{topic, mode} → candidate approaches to pick from
/api/start{topic, mode, approach, units, …} → create a project from a picked approach and run it (returns a job id)
/api/resume{project, force?} → resume a paused run
/api/action{project, action, …} → a one-off action: evaluate · seo · promote · evidence · tableread · restyle · polish · export
/api/pause{job} → pause the run cleanly at the next unit boundary
/api/review{project, unit, instruction} → record a review instruction for a paused unit
/api/settings{field, value} → set one setting (clamped, persisted)
/api/models{node, slug} → route a node (or default / all) to a model slug
/api/delete{project, confirm} → delete a project (confirm must equal the project id)

The dashboard’s automatic post-run tail reuses the same modules as the CLI: seo.py (audit + keyword pack + title optimization → seo_report.md) and promote.py (platform variants, headline variants, restyle → promo/*.md). Both are LOCAL-artifact-only and never post, schedule, or transmit anything.

Set WRITINGAGENT_FAKE=1 and every node returns deterministic placeholder output - the whole pipeline, state machine, and exporters run with no network and no API key. This is exactly how the test suite drives the app:

Terminal window
WRITINGAGENT_FAKE=1 python writingagent.py new --abstract "test" --pick 1
WRITINGAGENT_FAKE=1 python writingagent.py run

Everything is file-driven, so automation can template it:

  • config/settings.yaml - all tunable knobs (Settings)
  • config/models.yaml - per-node model routing (Model routing)
  • .env - OPENROUTER_API_KEY (required), FIRECRAWL_API_KEY (optional, for search_provider: firecrawl), HF_TOKEN (optional)
  • WRITINGAGENT_HOME - relocate the brain off synced/ephemeral folders

writingagent ships a stable, semver-guaranteed public API - the same engine the CLI and TUI drive, minus the plumbing. The import package is writingagent (the pip/distribution name is writing-agent); everything below is re-exported from the package root.

Terminal window
pip install -e . # from a clone
# or, once published: pip install writing-agent
from writingagent import write
result = write("How vector databases work", mode="article", export="docx")
print(result.export_path, result.word_count)

write() builds an autonomous agent, creates the project, runs it to completion, and exports it. It returns a WriteResult (project_id, mode, manuscript_path, export_path, export_format, word_count, status). Any setting can be passed as a keyword - num_sections=8, use_researcher=False, ….

Full lifecycle - create → run → inspect → export

Section titled “Full lifecycle - create → run → inspect → export”
from writingagent import Agent
agent = Agent(autonomous=True) # or Agent(user="me", mode="book", …)
project = agent.create("How vector databases work", mode="article", units=6,
requirements="audience: senior engineers; ~2000 words")
project.run(progress=print) # blocking; streams log lines
if project.status().done:
project.evaluate() # judged rubric + hard metrics
project.export("pdf") # -> pathlib.Path

Non-autonomous runs pause for a review you answer programmatically:

project = agent.create("...", autonomous=False)
st = project.run(progress=print)
while st.pending_review:
project.review(st.unit, "tighten the intro, add a citation")
st = project.run(progress=print)
ObjectConstructor / key members
write(topic, *, mode, units, export="pdf", requirements, …)one-shot → WriteResult
Agent(*, user="default", settings=None, models=None, autonomous=None, **overrides).plan() · .create() · .write() · .open() · .projects()
Project.run(progress=) · .status() · .review() · .revise() · .read() · .word_count() · .evaluate() · .table_read() · .memory() (books) · .consolidate() · .produce() · .export() · .delete()
Value typesApproach · Status · Evaluation · WriteResult
Constants / errorsEXPORT_FORMATS · MODES · WritingAgentError · ProjectNotFound

Project also exposes the properties id, mode, user, root, and manuscript_path. models= takes a ModelConfig or a slug string to route every node to one model; **overrides accepts any Settings field. Calls are synchronous and network-bound - pass progress= for a log callback, and wrap in asyncio.to_thread to call from async code.