leviath-cli 0.3.2

Command-line interface for Leviath agent framework
Documentation
[agent]
name = "writing-assistant"
version = "0.0.2"
description = "Research-backed writing - topic to polished draft, with a web-research stage, an interactive outline checkpoint, a draft⇄edit loop, and a final proofread pass"
entry_stage = "research"

[tool_permissions]
read_file  = "allow"
list_dir   = "allow"
web_search = "allow"
web_fetch  = "allow"
write_file = "ask"
bash       = "ask"

# ─── Stage 1: Research ────────────────────────────────────────────────────────
[stages.research]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Research the topic and gather supporting material"
available_tools = ["web_search", "web_fetch", "read_file", "list_dir", "context_append"]
max_iterations = 20
max_revisits = 2
system_prompt = """
Research the topic in `task` to prepare for writing. Gather:
- Key facts, data points, statistics
- Expert opinions and notable quotes
- Existing coverage; counterarguments and alternative perspectives

Use web_search + web_fetch for sources and read_file for local material; findings
land in `research`. For each source you'll rely on, append a bibliography line to
`sources_index` (context_append): `[n] <title> - <url/path>`. Organize by theme;
mark points well-supported vs speculative; flag anything needing verification. If
sent back, focus on the gap the user or outline identified.
"""

[stages.research.tool_routing]
default_region = "conversation"
[stages.research.tool_routing.overrides]
web_search = "research"
web_fetch  = "research"
read_file  = "research"
list_dir   = "research"

[stages.research.transitions.outline]
hint = "Enough material to structure the piece"
transform = "direct"

[stages.research.transitions.error_recovery]
condition = "error"
transform = "direct"

# ─── Stage 2: Outline (human-in-the-loop) ─────────────────────────────────────
[stages.outline]
mode = "interactive_points"
model = { models = [{ provider = "anthropic", model = "claude-opus-5" }, { provider = "openai", model = "gpt-5.5" }, { provider = "google", model = "gemini-3.1-pro-preview" }, { provider = "openrouter", model = "deepseek/deepseek-v4-pro" }, { provider = "ollama", model = "qwen3.6:27b" }] }
description = "Create and refine an outline with user input"
available_tools = ["ask_user_text", "read_file"]
max_iterations = 15
max_revisits = 4
transition_prompt = """
You've shown the outline and asked the user what to do:
- If they approved it, transition to 'draft'.
- If they asked to revise or expand it, transition back to 'outline', address
  their specific feedback (see the "detail" message in context), and present an
  updated outline before asking again.
- If they want to gather more material first, transition to 'research'.
- If they are done and want no piece written, transition to 'summary'.
"""
system_prompt = """
Propose an OUTLINE for the piece based on `research` - do NOT write the article
itself and do NOT create any files here. The draft stage writes the piece AFTER
the user approves this outline; your job is only to get the structure right.

Present, as your response text:
- Thesis / main argument
- Section structure with key points per section
- Where specific evidence or examples (with [n] source refs) will go
- Estimated length

Then let the user steer via the approval prompt. If they revise or expand, produce
an updated outline grounded in their actual feedback before asking again. Keep it
to an outline - no prose paragraphs of the finished piece.
"""

[stages.outline.transitions.draft]
hint = "Outline approved - write the draft"
transform = "compact"

[stages.outline.transitions.outline]
hint = "User asked to revise or expand - keep refining the outline"
transform = "direct"

[stages.outline.transitions.summary]
hint = "User is done - no piece to write"

[stages.outline.transitions.research]
hint = "User wants more material before committing to a structure"
transform = "compact"

[stages.outline.transitions.error_recovery]
condition = "error"
transform = "direct"

[[stages.outline.interaction_points]]
name     = "outline_approval"
prompt   = "Review the outline above. What would you like to do?"
required = true
style    = "multiple_choice"
options  = ["Approve - write the draft", "Revise - I'll describe changes", "Expand a section - I'll say which", "Research more first", "Abort - cancel this piece"]

# "Abort" ends the run immediately (engine-level, deterministic).
abort_options = ["Abort - cancel this piece"]

# Directives keep the run in the outline stage and tell the agent to gather the
# user's intent via a tool call before revising, rather than acting on the bare
# option label.
[stages.outline.interaction_points.directives]
"Revise - I'll describe changes" = "The user wants to revise the outline. Call the ask_user_text tool to ask exactly what they want changed, then present an updated outline before asking again."
"Expand a section - I'll say which" = "The user wants to expand a section. Call the ask_user_text tool to ask which section and what it should cover, then present an updated outline before asking again."

# ─── Stage 3: Draft ───────────────────────────────────────────────────────────
[stages.draft]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-opus-5" }, { provider = "openai", model = "gpt-5.5" }, { provider = "google", model = "gemini-3.1-pro-preview" }, { provider = "openrouter", model = "deepseek/deepseek-v4-pro" }, { provider = "ollama", model = "qwen3.6:27b" }] }
description = "Write the full draft"
available_tools = ["write_file", "read_file"]
max_iterations = 25
max_revisits = 2
system_prompt = """
Write the full draft (write_file) from the approved `outline`, backing claims with
evidence from `research` (cite [n] where relevant). Guidelines:
- Strong opening that hooks the reader
- Each section flows logically to the next
- Concrete examples over abstract statements
- Clear, direct prose - no filler or hedging
- Strong conclusion that ties back to the thesis

Write the complete piece. No placeholders or TODO markers.
"""

[stages.draft.transitions.edit]
hint = "Draft complete - self-edit"
transform = "compact"

[stages.draft.transitions.error_recovery]
condition = "error"
transform = "direct"

# ─── Stage 4: Edit (structural) ───────────────────────────────────────────────
[stages.edit]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-opus-5" }, { provider = "openai", model = "gpt-5.5" }, { provider = "google", model = "gemini-3.1-pro-preview" }, { provider = "openrouter", model = "deepseek/deepseek-v4-pro" }, { provider = "ollama", model = "qwen3.6:27b" }] }
description = "Structural edit for clarity, accuracy, and flow"
available_tools = ["read_file", "write_file", "context_append"]
max_iterations = 15
max_revisits = 2
transition_prompt = """
Decide what happens next:
- If there are STRUCTURAL problems needing a significant rewrite, respond with: draft
- If the structure and argument are sound and it's ready for a line-level pass,
  respond with: proofread
"""
system_prompt = """
Edit the draft for structure and substance (not line-level polish yet):
1. Accuracy - are claims supported by `research`? Any overstatements?
2. Clarity - is each idea understandable on first read?
3. Flow - does each section lead naturally to the next?
4. Concision - cut anything that doesn't earn its place
5. Voice - consistent, appropriate for the audience?
6. Opening/closing - do they land?

Make the edits directly (write the improved version). Note any substantive changes
in `revisions` (context_append). Hand to proofread only once the structure is sound.

If `error_report` says a previous stage hit its iteration cap, that stage was cut
off before finishing - check the draft for missing sections before polishing it.
"""

# Custom transform: keep task/outline/research/sources pinned, compact the draft
# discussion into a rewrite brief, clear scratch before rewriting.
[stages.edit.transitions.draft]
hint = "Structural issues found - needs a significant rewrite"
transform = "custom"
[stages.edit.transitions.draft.transform_config]
carry = ["task", "style_guide", "outline", "research", "sources_index"]
compact = ["draft"]
clear = ["scratch"]
compact_prompt = "Summarize the structural problems as a numbered rewrite brief."

[stages.edit.transitions.proofread]
hint = "Structure is sound - do the final line-level pass"
transform = "compact"

[stages.edit.transitions.error_recovery]
condition = "error"
transform = "direct"

# ─── Stage 5: Proofread (terminal) ────────────────────────────────────────────
# Final line-level pass. Hands off to the output stage when the piece is clean;
# can bounce back to edit if it turns up a substantive problem.
[stages.proofread]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Final line-level proofread and fact-check"
available_tools = ["read_file", "write_file", "web_fetch"]
max_iterations = 10
max_revisits = 1
transition_prompt = """
Final pass done. Decide:
- If you found a SUBSTANTIVE problem (a wrong fact, a broken argument) that needs
  a structural fix, respond with: edit
- If the piece is clean and ready for the reader, respond with: summary
"""
system_prompt = """
Final line-level pass following `style_guide`:
- Grammar, spelling, punctuation, consistent terminology and formatting
- Every citation resolves to a `sources_index` entry; spot-check any shaky fact
  with web_fetch and correct or hedge it
- Tighten wording; kill remaining filler
Write the final polished version. Don't introduce new claims. Ship it (DONE) once
clean - don't over-polish.
"""

[stages.proofread.transitions.summary]
hint = "The draft is finished"

[stages.proofread.transitions.edit]
hint = "A substantive problem surfaced - back to structural editing"
transform = "compact"

[stages.proofread.transitions.error_recovery]
condition = "error"
transform = "direct"

# ─── Stage 6: Error recovery ──────────────────────────────────────────────────
[stages.error_recovery]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Recover from a failed research fetch or file write"
available_tools = ["read_file", "bash"]
max_iterations = 8
max_revisits = 1
system_prompt = """
Something failed (a research fetch or a file write). The `error_report` region
holds the error text the runtime captured - read it first, note a workaround,
then return to draft to continue with the material available.
"""

[stages.error_recovery.transitions.draft]
hint = "Recovered - continue drafting"
transform = "compact"

[compaction]
provider = "anthropic"
model = "claude-sonnet-5"

# ─── Final output ────────────────────────────────────────────────────────────
# Terminal. `mode = "output"` grants `submit_output`, requires the call, and
# lets the run end here. No file tools on purpose: this stage reports, it does
# not work.
[stages.summary]
mode = "output"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "google", model = "gemini-3.5-flash" }, { provider = "openrouter", model = "deepseek/deepseek-v4-flash" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Say what was written"
max_iterations = 8
system_prompt = """
Say what you produced, for whoever asked for it.

Name the file, say what it is and roughly how long, and note anything you had to
decide on your own - a structural choice, a section you left thin, a fact you
could not verify. Those are the things the reader needs to check.
"""

[stages.summary.transitions]

# ─── Context layout ───────────────────────────────────────────────────────────
[context.regions]
task          = { kind = "pinned", budget = "2%", max_tokens = 2000, required = true, seed = "task", required_message = "Describe what to write (topic, audience, length) via --task." }
style_guide   = { kind = "pinned", budget = "1%", max_tokens = 1500, seed = "style" }
sources_index = { kind = "pinned", budget = "3%", max_tokens = 4000 }
outline       = { kind = "pinned", budget = "4%", max_tokens = 4000 }

research      = { kind = "temporary",      budget = "25%", max_tokens = 40000 }
draft         = { kind = "compacting",      budget = "30%", compact_at = "80%", threshold_tokens = 35000, max_tokens = 50000 }
draft_history = { kind = "compact_history", source_region = "draft", budget = "3%", max_tokens = 10000 }
revisions     = { kind = "sliding_window", max_items = 15, budget = "4%", max_tokens = 5000 }

conversation  = { kind = "sliding_window", max_items = 30, budget = "12%", max_tokens = 15000, strategy = "bulk", overflow = 10 }
scratch       = { kind = "clearable", budget = "5%", max_tokens = 6000 }

# Written by the RUNTIME on an abnormal stage ending (issue #154): a failed
# inference call's error text, or a note that a stage hit its iteration cap.
# Pinned so it survives the edge transform into the stage that acts on it.
error_report  = { kind = "pinned", budget = "1%", max_tokens = 2000 }