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.
One-shot CLI
Section titled “One-shot CLI”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):
writing-agent new --abstract "The psychology of decision fatigue" --pick 1writing-agent runwriting-agent export --format pdfwriting-agent read --manuscriptThe one-shot write command does the whole thing - interview, autonomous run, export - in a
single invocation:
python writingagent.py write --abstract "..." --chapters 6 --max-revisions 2Flags map to the same settings you’d change with /set. See the full
Commands reference.
Web dashboard HTTP API
Section titled “Web dashboard HTTP API”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.
| Route | Returns |
|---|---|
/ · /index.html · /static/* | the single-page app + whitelisted assets |
/api/state | user, 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/skills | the 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.
| Route | Body → 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.
CI-safe with fake mode
Section titled “CI-safe with fake mode”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:
WRITINGAGENT_FAKE=1 python writingagent.py new --abstract "test" --pick 1WRITINGAGENT_FAKE=1 python writingagent.py runConfiguration as code
Section titled “Configuration as code”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, forsearch_provider: firecrawl),HF_TOKEN(optional)WRITINGAGENT_HOME- relocate the brain off synced/ephemeral folders
Python API
Section titled “Python API”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.
pip install -e . # from a clone# or, once published: pip install writing-agentOne-shot - topic in, finished file out
Section titled “One-shot - topic in, finished file out”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 linesif project.status().done: project.evaluate() # judged rubric + hard metrics project.export("pdf") # -> pathlib.PathHuman-in-the-loop - review from code
Section titled “Human-in-the-loop - review from code”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)Surface
Section titled “Surface”| Object | Constructor / 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 types | Approach · Status · Evaluation · WriteResult |
| Constants / errors | EXPORT_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.