Terminal app that writes exploratory essays one sentence per pass, with tool-logged traces.
It is designed to feel like an essayist thinking on the page: incremental drafting, frequent revision, paragraphing discipline, and an editor cycle before final stop.
- Runs in the terminal (Bun + TypeScript).
- Uses Vercel AI SDK tool-calling against Moonshot Kimi (via Anthropic-compatible client).
- Writes and edits
essay.mdin a per-run folder (one run = one folder underruns/by default). - Enforces a strict per-pass mutation budget: net sentence delta must be
<= +1per pass. - Logs everything to:
trace.md(machine-readable JSON)trace.human.md(human-readable Markdown)
- Writer: writes/edits the essay using tools (one sentence per pass).
- Fresh writer (optional): a second writer with the same rubric but no conversation history; reads
essay.md+ a summary oftrace.md, then applies revisions. - Editor (optional): inserts an
Editor Feedbackblock intoessay.md. - Writer revision pass: the writer must address feedback and remove it (
clear_editor_feedback) beforeself_stopwill be accepted.
- Bun (project tested with Bun
v1.3.8) - Env var:
KIMI_API_KEY
Optional env vars:
KIMI_MODEL(default isk2p5)
bun installInteractive (default starts in plan mode):
bun run startStart directly in write mode and auto-continue without prompting each pass:
bun run start:writeResume / continue an existing run folder (forces reading essay.md first):
bun run start --folder runs/run-20260214-125927-ieytq9During the run you can type:
/write: switch to write mode/plan: switch to plan mode/done: stop the session
Inside each run folder:
essay.md: the essay (canonical target)trace.md: machine-readable trace JSON (passes, tool calls, stats)trace.human.md: human-readable trace
The agent is sandboxed to markdown files in the run directory.
- Terminal output: streamed thinking (when provider emits it) plus a TUI showing each tool call and result.
- Files:
essay.md,trace.md(machine JSON),trace.human.md(human Markdown).
read_file({ path }): read a markdown file (typicallyessay.md).list_paragraphs({ path }): list paragraphs with 1-based indices (for targeting/reordering).list_sentences({ path }): list sentences with 1-based indices (for precise edits).
create_file({ path }): create a new.mdfile (used foressay.mdor notes).write_sentence({ path, sentence, position, targetSentenceIndex? }): add exactly one sentence at a location (append/before/after).replace_sentence({ path, sentenceIndex, sentence }): replace one sentence by index (preferred for rewrites).delete_sentence({ path, sentenceIndex }): delete one sentence by index.string_replace({ path, search, replace, occurrence? }): exact substring replacement (small surgical edits).start_new_paragraph({ path }): insert a blank line to start a new paragraph.split_paragraph({ path, paragraphIndex, afterSentenceInParagraph }): split an overlong paragraph.arrange_paragraph({ path, fromIndex, toIndex }): move one paragraph block.reflect_paragraph({ path, paragraphIndex, focus }): read-only critique of one paragraph.reflect_essay({ path, focus }): read-only critique of the whole essay.clear_editor_feedback({ path }): remove the embedded editor feedback block after revisions.self_stop({ reason }): request to end the session (accepted only when completion thresholds are met).
- Same tools as the Writer, plus:
read_trace_summary({ path: "trace.md", ... }): summarize the machine trace to understand how the essay was produced.fresh_writer_stop({ reason }): stop the fresh-writer stage.
comment_essay({ path, ... }): insert/update anEditor Feedbackblock inessay.md.editor_stop({ handoff }): end the editor stage and hand instructions back to the writer.
You can tune behavior without touching code by editing pg.config.json at the repo root.
Common knobs:
runtime.maxPasses: max number of passes (hard cap: 500)cli.defaultMode:planorwritecli.autoContinueWrite: auto-continue in write mode without promptingcli.runsRoot: where run folders go (relative path, defaultruns)stop.minSentences,stop.minParagraphs: minimum thresholds beforeself_stopcan be acceptedparagraph.maxSentencesPerParagraph: runtime enforces splitting beyond this thresholdstall.*: stop guards for blank / tool-less loopsfreshWriter.*: enable/limit the fresh-writer review pass stageeditor.*: enable/limit editor cyclessubagents.enabled: enable/disable internal planner/reflection subagent calls
To analyze a run’s trace.md:
bun run trace:analyze runs/run-20260214-125927-ieytq9This prints tool usage frequency, reflection patterns, and other heuristics that help debug prompting/loops.
bun run startbun run start:writebun testbun run typecheckbun run trace:analyze <run-folder|trace.md>