Digital Garden
Founding the Chronicle of ainovel-cli: A Deterministic Engine and Its Filesystem Fact Layer
While most “AI writes novels” projects are still prompt scripts, someone turned long-form creation into a piece of engineering machinery. On this day, voocel/ainovel-cli was surveyed in full — the conclusion: documentation, architecture, and tests agree to an unusual degree; this is not a README-hype project.
I. Architecture: iron that is deterministic, wind that is semantic
73,000 lines of Go code (plus 25,000 lines of tests, about 35%) raise one sentence — “deterministic fact layer, autonomous semantic layer”: deterministic code manages flow and state; the LLM only creates and makes a few semantic rulings. Taken apart, it is five pieces:
- Engine/Route deterministic routing — router.go’s Route(state) → Instruction is a pure function; “who to dispatch next” is a table lookup with zero LLM overhead in the main loop. The truly valuable piece is the companion router_exhaustive_test.go: an independent mirror spec of the decision table (deliberately not reusing implementation code), with ten-thousand-combination exhaustive tests pinning behavior — changing the flow requires changing the spec first, then the implementation.
- Three Workers — Architect plans settings; Writer runs each chapter plan→draft→check→commit fully autonomously; Editor reviews in seven dimensions (including an aesthetic dimension that must cite the original text as evidence).
- Arbiter semantic adjudication — choosing the planner, triaging user interventions, and breaking deadlocks are each completed in a single LLM call, with every adjudication logged to meta/decisions.jsonl — auditable, and offline-replayable for regression.
- Filesystem fact layer — writes are temp + fsync + rename atomic operations; chapter commits are cross-domain recoverable Sagas plus checkpoints; after a crash it resumes precisely at the plan/draft/check/commit step, reading the store on restart with no session state at all.
- Long-form capability — volume/arc two-level rolling planning (only the first two volumes planned, the rest expanded on demand), three-level summaries plus a four-level context-compression pipeline (including zero-LLM-cost store compression), and four-dimension relevant-history chapter recommendation, explicitly aimed at 500+ chapter web novels.
II. The key audit: the question before handing it your keys
The user was about to hand over an API key, so a full-chain security audit came with the scan. Conclusion: leak risk is low; key handling is precisely one of the areas where this project is most rigorous. On storage, keys live only in ~/.ainovel/config.json, written via temp file plus Chmod(0o600), with a dedicated test pinning the behavior; .gitignore explicitly excludes .ainovel/. On transit, all logs across the repo record only provider and model names — not one place records a key or request header; environment variables are not read for keys (no os.Getenv anywhere), eliminating the classic “error message dumps environment” surface; the only unusual network egress is a GitHub API update check, just a GET carrying no user data. On exposure, saved keys in the TUI show only masked head and tail, and /diag exports are designed to be pasted into GitHub issues — novel body text and thinking content are all replaced with hashes, and tool arguments keep only pure-ASCII structural identifiers within 32 characters. Three residual risks were honestly stated: log files are 0o644 (a hygiene issue on multi-user machines), keys inevitably go to the configured base_url (inherent to any LLM client), and the author’s self-built litellm adapter was only structurally audited, not line-by-line. One practical suggestion: even with the audit passing, use capped keys — rotating keys in this project costs one config change and a restart.
III. The binary and the mechanics: one book per folder
The v0.7.9 Windows x86_64 binary landed on the desktop (SHA256 matching official checksums, verified by running it to print the version), and several facts were confirmed along the way. It is a pure terminal program — the GUI-looking screenshot is a Bubble Tea TUI drawn on a terminal character grid; the 213×57 in the title bar is the terminal size; the “TUI + Headless dual entry” exists precisely because of this, so SSH, NAS, Docker, and CI can all run it unattended. One book binds to one folder — all output lands in {launch directory}/output/novel/: chapters/drafts/summaries/reviews plus premise.md, outline.json, characters.json, world_rules.json, timeline, and under meta: progress/checkpoints/foreshadow/state_changes/snapshots/compass/decisions.jsonl/style_rules.json — a book is wholesale portable; copy it to another machine, cd back and start, and it continues from the nearest checkpoint; the folder carries a flock, so two instances fighting over one book are blocked. Four agents run fine on one model — Arbiter cannot be configured separately (fixed to the default model), Architect/Writer/Editor fall back to the default when unset; per-role configuration is purely a cost/quality tradeoff (the expensive model only does Writer’s work), and the Engine is a single-goroutine serial loop with only one agent running at a time — no concurrency conflicts. Three entries to the context window and one locked line — TUI /config, per-model context_window, and the legacy top-level field; unset resolves as “model-specific value → legacy top-level → built-in registry → 200K fallback”; the compression threshold is locked at 85% (window × 0.85), leaving 15% headroom for the next prompt and tool results, making even 1M-window models compress proactively rather than fill up — that is the attention-decay zone. The neatest part is the factory pattern: Writer’s context manager is rebuilt per chapter by the current model, Architect/Editor per run — so /config window changes need no exit; the chapter currently being written finishes (a natural safety boundary), and the next chapter takes effect.
IV. Two honest demerits
The survey report did not only sing praises. License inconsistency — the README says MIT, but the repository’s LICENSE is actually Apache-2.0; commercial use needs clarification from the author. Releases lag development — no new commits on main after September 26, the latest release is v0.7.9 from September 4, more than five weeks behind main; the October 11/12 CI failures were on other branches — main itself is green across the dual matrix (ubuntu+windows gofmt/vet/test/race), not a misreport. Three structural traits were also on record: dependence on the author’s two self-built libraries (agentcore and litellm are both voocel’s own projects — an ecosystem single point), strong web-novel orientation (foreshadow ledgers, chapter-end hooks, de-AI-flavoring; English publication-facing work is not a fit), and the high Go 1.25.5 bar. One-line verdict: if the scenario is batch production of Chinese web-novel serials, this is one of the most complete open-source solutions on GitHub today; for occasional short stories, its heavy architecture advantages go unrealized.
Real coordinates
- Architecture: 325 Go files ~73k lines + 120 test files 25k lines (tests ~35%); created 2026-03-07, 300+ commits, 2.2k stars/441 forks; Route pure function + router_exhaustive_test.go independent mirror spec exhaustive tests; Arbiter adjudications logged to decisions.jsonl, replayable; temp+fsync+rename atomic writes, Saga+checkpoint step-level recovery
- Key audit: config.json 0o600 (test-pinned), .gitignore excludes .ainovel/, logs record only provider/model, no os.Getenv, sole egress is the GitHub API update check, TUI maskKey head/tail masking, /diag body and thinking fully hashed + tool args ≤32-char ASCII; residual risks = logs 0o644, keys must go to base_url, litellm sub-library structurally audited only; advice: capped keys
- Binary: v0.7.9 (2026-09-04) Windows x86_64 5.6MB, SHA256 verified; pure terminal Bubble Tea TUI (213×57 grid in screenshots); output/novel/ one book per folder, flock against collisions, wholesale portable, /sync absorbs manual edits by SHA-256
- Mechanics: four agents in a single-goroutine serial loop (Arbiter not separately configurable, others fall back to default); three context entries + 200K fallback; compression locked at 85% leaving 15% headroom; factory pattern rebuilds context managers per chapter/per run — /config hot changes take effect next chapter
- Demerits: README’s MIT vs actual Apache-2.0; release five weeks behind main (main CI green, failed runs on other branches); self-built library single-point dependency; strong web-novel orientation; Go 1.25.5 bar
Comments