leviath-cli 0.3.9

Command-line interface for Leviath agent framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
[agent]
name = "coder"
version = "0.1.0"
description = "Coding agent: discover the repo, plan with your sign-off, optionally spike, implement, and review, with stuck detection and graph-based recovery"
entry_stage = "discover"

# Global tool permissions for this agent: write tools require approval by default.
# Use `lev run default --yolo` to skip all approvals, or `--allow bash` for selective override.
[tool_permissions]
read_file = "allow"
list_dir  = "allow"
write_file = "ask"
edit_file  = "ask"
bash       = "ask"

# ─── Stage 1: Discover ───────────────────────────────────────────────────────
# Orient before planning: map the codebase and synthesize the verification
# workflow the later stages must follow. Runs before the human sees anything, so
# the plan they approve is grounded in what this repo actually is. Cheap model,
# hard iteration cap, and a single non-error edge (auto-followed, no routing call).
[stages.discover]
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 = "Map the codebase and synthesize a verification workflow"
available_tools = ["read_file", "list_dir", "bash", "context_write"]
max_iterations = 8
max_revisits = 2
system_prompt = """
Before any planning, answer two questions about THIS repository: what is it, and
how do I verify work in it? Do not plan or edit here.

Start from what you already have - do not rediscover it:
- `repo_files` holds the tracked file list (empty if this isn't a git repo).
- `architecture` is pre-loaded with design docs, `constraints` with any
  caller-supplied limits.
- If `.leviath/discovery.md`, `CLAUDE.md`, `AGENTS.md` or
  `.github/copilot-instructions.md` exist, read them and treat them as
  authoritative - they were written for exactly this purpose.

Then fill the gaps with list_dir/read_file, and use bash ONLY to interrogate the
build/test tooling read-only (e.g. `pytest --collect-only -q`, `cargo test
--list`, `npm run`). Do not modify anything. You have few iterations - spend
them on the area the task touches, not a full tour.

This stage does not build the deliverable. If you find yourself creating the
thing the task asked for, stop - that is `implement`'s job, and doing it here
means the plan gets written against work you have already done rather than
against the repository. If a scratch file is genuinely unavoidable to answer a
question about the tooling, delete it before you finish, and say in `discovery`
what you created and that you removed it. A file left behind reads to every
later stage as pre-existing code.

Write `discovery` (context_write) covering:
- language, build system, and how to build
- the test runner, the command to run the WHOLE suite, and the command to run a
  SINGLE test or file (this one matters most downstream)
- directory layout and where the code for this task lives
- conventions worth obeying that aren't already in `conventions`

Then classify the project into exactly one tier and write `workflow`
(context_write) with the tier, the concrete commands, and the completion bar:

- TIER 1 - no tests, no CI, nothing to verify against. The plan must include
  BUILDING verification: name the smoke test or assertion to write, and how to
  run it. Say plainly that there is no baseline to compare against.
- TIER 2 - some tests exist but coverage is patchy. Name the tests that already
  cover the area being changed, and the gap a new test should fill.
- TIER 3 - rich test suite. Name the exact subset to run for this task (a full
  suite run per edit is too slow) and the full-suite command for the final pass.

`workflow` must end with three literal lines the later stages execute verbatim:
  BASELINE: <command to run BEFORE any edit>
  VERIFY: <command to re-run after each change>
  DONE WHEN: <the completion bar, including "no regressions vs baseline">

If there is genuinely no way to verify (tier 1 with no runnable code yet), say
so explicitly in `workflow` rather than inventing a command that won't run.
"""

# Scan output is bulky and single-use - park it in clearable scratch, not the
# knowledge regions the later stages read.
[stages.discover.tool_routing]
default_region = "conversation"
[stages.discover.tool_routing.overrides]
read_file = "codebase"
list_dir  = "codebase"
bash      = "scratch"

[stages.discover.transitions.plan]
hint = "Codebase mapped and verification workflow synthesized"
transform = "direct"

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

[stages.discover.transitions.summary]
condition = "dead_end"
# Taken only when every other way out is revisit-exhausted, so it is never a
# route the model can pick early. Without it the run errors as dead-ended and
# the work done so far is lost; with it the run still hands back an answer.

# ─── Stage 2: Plan ───────────────────────────────────────────────────────────
# Read-only exploration culminating in a structured plan.
# The user is shown the plan and must approve, request revisions, or add detail.

[stages.plan]
mode = "interactive_points"
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 = "Explore the codebase and produce a step-by-step implementation plan"
available_tools = ["read_file", "list_dir", "ask_user_text", "ask_user_choice", "edit_document"]
# Attended, planning is where this agent most wants its user, and all three of
# these are available above. They are deliberately NOT in required_tools: an
# unattended run drops them with the rest of the blocking tools, so `--yolo`
# plans and proceeds instead of parking on a question nobody is there to
# answer. Put them back if you would rather an unattended run stop and wait.
required_tools = []
max_iterations = 20
max_revisits = 6
# "Abort" is handled deterministically by the engine (see abort_options on the
# plan_approval interaction point), so allow_complete is not required here - and
# leaving it on let a model end the whole run from `plan`.
#
# That is not hypothetical: a model that had created a file during `discover`
# (which has `bash`) read it back here, concluded "already created and verified
# in the previous step - no further action is needed", and completed. The user's
# correction to the plan was applied to the document and then ignored, because
# the model had already decided there was nothing to do. `implement` never ran.
#
# Planning is not a place a run should be able to end. Every edge out of here
# goes to `prototype`, `implement` or back to `discover`; the abort path is the
# engine's, not the model's.
allow_complete = false
transition_prompt = """
You've shown the plan to the user and asked them to approve it, request
revisions, or edit it directly.

- If they approved the plan, transition to 'implement' - UNLESS the plan rests
  on an assumption you have not verified (you are unsure an API/library behaves
  as assumed, or `discovery` did not pin down where the behavior you must change
  lives). In that case transition to 'prototype' to settle it with a small spike
  first. The user approved the goal, not your guesses about the codebase; a
  spike that corrects the plan serves them better than forty edits in the wrong
  file. Only detour when you expect to learn something that would change what
  you write.
- If they asked for revisions or edited the plan, transition back to 'plan',
  incorporate their feedback (see the "directive"/"detail" messages in
  context), and produce an updated plan before asking again.
"""
system_prompt = """
You are the planning stage of a code assistant.

Your job:
1. Read `discovery` and `workflow` first - the discover stage already mapped this
   codebase and chose how the work will be verified. `architecture` holds design
   docs and `constraints` any caller-supplied limits (tech stack, timeline,
   budget). Explore with list_dir/read_file only to fill real gaps left by those.
2. Think through the implementation approach, respecting the constraints.
3. Produce a concise, numbered plan:
   - Files to create or modify
   - What each change does
   - Key decisions and trade-offs
   - How the change will be verified, using `workflow`'s VERIFY command. If
     `workflow` is TIER 1, the plan must include writing that verification.

If the task is genuinely ambiguous (e.g. missing requirements, or a real
fork in approach you can't resolve yourself), use ask_user_text or
ask_user_choice to ask BEFORE producing the plan - don't guess on things
only the user can answer. Don't ask about things you can reasonably decide
yourself.

This stage does not touch the codebase. You have no tool here that writes or
edits a file, and calling one is refused - planning that starts writing is how a
run ends up with code nobody approved. Describe the change; `implement` makes it.
If a claim in the plan needs proving before it is worth writing down, that is what
`prototype` is for.

Be specific - the implement stage will execute exactly what you write here.
End with:

## Plan
<numbered list of steps>

## Files
<list of files to create/modify>
"""

[stages.plan.tool_permissions]
read_file = "allow"
list_dir  = "allow"
ask_user_text   = "allow"
ask_user_choice = "allow"
edit_document   = "allow"

# Only passive reads feed the persistent codebase region; everything else stays
# in conversation.
[stages.plan.tool_routing]
default_region = "conversation"
[stages.plan.tool_routing.overrides]
read_file = "codebase"
list_dir  = "codebase"

[stages.plan.transitions.implement]
hint = "Plan approved and the approach is verified - proceed to implementation"

[stages.plan.transitions.prototype]
hint = "Plan approved but rests on an unverified assumption - spike it first"

[stages.plan.transitions.plan]
hint = "User asked for revisions or more detail - keep planning"

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

[stages.plan.transitions.summary]
condition = "dead_end"
# Taken only when every other way out is revisit-exhausted, so it is never a
# route the model can pick early. Without it the run errors as dead-ended and
# the work done so far is lost; with it the run still hands back an answer.

[[stages.plan.interaction_points]]
name     = "plan_approval"
prompt   = "Review the plan above. What would you like to do?"
required = true
# Attended, this is the checkpoint that matters: everything after it writes
# code, so a person sees the plan first. Unattended, it resolves as approved
# rather than stranding the run, because `--yolo` means nobody is there to
# answer and this agent is also the one CI reaches for. Set
# `unattended = "ask"` here if you would rather an unattended run stop and wait.
style    = "multiple_choice"
options  = ["Approve - proceed to implementation", "Revise - I'll describe changes", "Add detail - expand a section", "Abort - cancel this run"]

# The pinned region that holds the authoritative plan. Each time this point is
# presented, the current plan - the model's output, or the user's direct edit -
# REPLACES this region, so revisions build on the current version (the user's
# edits included) instead of regenerating the plan from the task.
document_region = "plan"

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

# "Add detail" opens the current plan in an editable field (engine-level,
# deterministic) so the user modifies it directly - no dependence on the model
# choosing to call an edit tool. The edited text becomes the authoritative plan.
edit_options = ["Add detail - expand a section"]

# Directives keyed by option label. Selecting one keeps the run in the plan
# stage and injects the directive into the agent's context so it drives the
# next step via a tool call (deterministic routing, agent-driven capture).
[stages.plan.interaction_points.directives]
"Revise - I'll describe changes" = "The user wants to revise the plan. Call the ask_user_text tool to ask exactly what they want changed. Then start from the CURRENT plan (in the 'plan' section of your context - it already includes any edits the user made directly) and apply ONLY the requested change, preserving every other detail verbatim (wording, file names, and exact strings the user chose). Do not revert earlier user edits or 'correct' them back to defaults. End with the '## Plan' and '## Files' sections so it can be re-approved."

# ─── Stage 2b: Prototype (elective) ──────────────────────────────────────────
# NOT a mandatory hop - `plan` only routes here when the approved plan rests on
# an assumption the agent has not actually verified. It buys information
# cheaply: prove or kill the riskiest assumption, record what was ruled out, and
# correct the plan so `implement` executes evidence instead of a guess. The
# user's approved GOAL is never renegotiated here, only the technical route.
[stages.prototype]
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 = "devstral:24b" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Spike the riskiest assumption in the approved plan before executing it"
available_tools = ["read_file", "list_dir", "write_file", "edit_file", "bash", "context_write", "context_append"]
max_iterations = 15
max_revisits = 2
transition_prompt = """
Spike complete. Based on what you actually observed:
- Respond with `implement` if a hypothesis held up - you now know where the
  change belongs and what it should do.
- Respond with `plan` if every hypothesis failed. The approved plan rests on a
  wrong premise, so the user needs to see and approve a corrected one rather
  than have you execute a plan you know is wrong.
"""
system_prompt = """
You are spiking, not implementing. The plan in `plan` was approved by the user,
but it assumes things about this codebase you have not verified. Pick the ONE
assumption that would cost the most to discover was wrong, and settle it with
the smallest thing that actually runs.

1. State the assumption in a sentence.
2. Prove or disprove it: a throwaway script, a failing test that reproduces the
   bug, a bash one-liner that greps for where the behavior really lives. Use the
   VERIFY command from `workflow` - do not invent your own. For a BUGFIX the
   spike IS the reproduction; do not move on without one.
3. Append the result to the `prototypes` region (context_append): the
   assumption, the verdict, the exact command or snippet that settled it, and
   the file paths you confirmed. Record what you RULED OUT as carefully as what
   worked - a dead end you don't write down gets retried.
4. Before you leave, write the corrected approach to the `plan` region
   (context_write, key "plan"). Preserve the user's intent and any wording they
   chose; correct only the technical route. That plan is what runs next.

Keep this to a handful of iterations, and throw away scaffolding you created
purely to test a hypothesis. You are buying information, not shipping code.
"""

[stages.prototype.tool_routing]
default_region = "conversation"
[stages.prototype.tool_routing.overrides]
read_file  = "codebase"
list_dir   = "codebase"
bash       = "test_results"
write_file = "implementation"
edit_file  = "implementation"

[stages.prototype.transitions.implement]
hint = "A hypothesis held up - implement it properly"
transform = "compact"

[stages.prototype.transitions.plan]
hint = "Every hypothesis failed - the plan's premise is wrong, re-plan with the user"
transform = "compact"

[stages.prototype.transitions.reassess]
condition = "stuck"
stuck_after_iterations = 12
stuck_after_same_file_edits = 4
hint = "The spike is going in circles - step back"
transform = "direct"

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

[stages.prototype.transitions.summary]
condition = "dead_end"
# Taken only when every other way out is revisit-exhausted, so it is never a
# route the model can pick early. Without it the run errors as dead-ended and
# the work done so far is lost; with it the run still hands back an answer.

# ─── Stage 3: Implement ──────────────────────────────────────────────────────
# Executes the approved plan. Write/edit/bash require tool-approval unless
# the user passes --yolo or stage-level overrides.

[stages.implement]
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 = "devstral:24b" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Write code according to the approved plan"
available_tools = ["write_file", "read_file", "edit_file", "list_dir", "bash", "ask_user_text", "ask_user_confirm", "context_append"]
max_iterations = 50
max_revisits = 5
# The ask_user_* tools suspend the run until a person answers, which is
# deliberate here: this stage asks rather than guessing when the approved plan
# turns out to be underspecified. Declared so `lev validate` reports it as a
# choice rather than an oversight.
allow_blocking_tools = true
system_prompt = """
You are the implementation stage. The plan in the `plan` region has been approved.
Execute it step by step:
- Use write_file to create new files and edit_file to modify existing ones. Do
  NOT edit files through bash - no `sed -i`, no `tee`, no `>`/`>>` redirection,
  no here-docs. Only write_file/edit_file are recorded in the `implementation`
  region the reviewer reads, and this stage will not hand off until at least one
  of them has landed.
- Use bash to run tests or verify the build after writing - output goes to
  `test_results`.

Follow the workflow the discover stage synthesized in `workflow`. It ends with
three literal lines - BASELINE, VERIFY and DONE WHEN. Execute them:

1. BASELINE, before your FIRST edit. Run the BASELINE command and context_write
   the result to `baseline`: which tests pass and which already fail. You cannot
   tell a regression from a pre-existing failure without this. (TIER 1 / no
   runnable suite: write "no baseline - nothing to run yet" and build the
   verification the plan calls for as your first change.)
2. VERIFY after each logical change, not just at the end.
3. Compare every VERIFY against `baseline`. If a test that passed in `baseline`
   now fails, you broke it: say so explicitly - "I broke <test> with my change to
   <file>, investigating" - and fix it before writing anything else.
4. Before finishing, run the full-suite command from `workflow` once.

You are NOT done because most tests pass. You are done when DONE WHEN is met:
the target tests pass AND nothing that passed in `baseline` fails now. If tests
are still failing, say how many and keep going - do not stop early.

As you work, keep two running logs so the review stage (and any restart) has
the full picture without re-scanning:
- `decisions` (context_append): each non-obvious design/architectural choice and
  the WHY behind it - invaluable when review questions a call.
- `changelog` (context_append): one line per file created/modified, so review
  knows exactly what to check.

If you hit a genuine ambiguity the plan didn't resolve (e.g. conflicting
requirements, or a destructive/hard-to-reverse action you're unsure about),
use ask_user_text or ask_user_confirm to check before proceeding - don't
silently guess on something the user would want a say in.

Work methodically through each plan step. After all steps, give a concise summary
of what was created/modified, the final test counts, and an explicit statement
that nothing regressed against `baseline`.
"""

# Large read output persists in `codebase`; test output persists in `test_results`
# (the review stage reads it). Routed results leave a short pointer in conversation
# (paired with their tool_use) with the full output as text in the region.
#
# write_file/edit_file confirmations persist in `implementation`, alongside the
# hand-written `changelog`: it is a mechanical record of every file touched, it
# survives `conversation` eviction, and - being persisted context - it satisfies
# the transition gates below after a daemon restart, when per-stage counters are
# gone.
[stages.implement.tool_routing]
default_region = "conversation"
[stages.implement.tool_routing.overrides]
read_file  = "codebase"
list_dir   = "codebase"
bash       = "test_results"
write_file = "implementation"
edit_file  = "implementation"

# Both non-error edges are gated: an agent that explored through bash and changed
# nothing is sent back for another pass rather than handing an untouched workspace
# to review - or slipping out sideways via the `plan` edge, which can itself end
# the run (issue #107).
[stages.implement.transitions.review]
hint = "Implementation complete, ready for review"
transform = "compact"
gate = { require_modifications = true, region = "implementation" }

# Going back to planning with nothing written is sometimes the right call, so this
# edge only asks once before letting a genuinely stuck agent through.
[stages.implement.transitions.plan]
hint = "Need to fundamentally rethink the approach"
transform = "compact"
gate = { require_modifications = true, region = "implementation", max_attempts = 1 }

# Runtime escape hatch (issue #106). Distinct from the voluntary `plan` edge
# above: that one requires the agent to NOTICE it is lost, which is exactly what
# fails when it is stuck. This one fires on measured behavior instead, and is
# ungated - a stuck agent is not helped by being told to write more.
[stages.implement.transitions.reassess]
condition = "stuck"
stuck_after_iterations = 20
stuck_after_minutes = 15
stuck_after_same_file_edits = 5
hint = "No forward progress - step back and reassess"
transform = "custom"
# `custom`, not `compact`: a plain compact edge would summarize EVERY
# stage-specific region including `test_results`, the raw evidence reassess most
# needs. Compact only the conversation and leave the rest intact.
[stages.implement.transitions.reassess.transform_config]
carry = ["discovery", "workflow", "task", "constraints", "architecture", "plan", "codebase", "implementation", "prototypes", "changelog", "decisions", "errors", "test_results", "stuck_report", "error_report"]
compact = ["conversation"]
clear = ["scratch"]
compact_prompt = "Summarize what was attempted in this stage: which files were edited and how often, which tests were run and their outcome, and the last point at which anything demonstrably worked."

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

[stages.implement.transitions.summary]
condition = "dead_end"
# Taken only when every other way out is revisit-exhausted, so it is never a
# route the model can pick early. Without it the run errors as dead-ended and
# the work done so far is lost; with it the run still hands back an answer.

# ─── Stage 4: Review ─────────────────────────────────────────────────────────
# Autonomous review. Transitions back to implement if issues found, ends the
# run (allow_complete + "DONE") when the code is clean, or routes to
# error_recovery if the review itself errors out.

[stages.review]
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 = "devstral:24b" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Review the implementation and evaluate code quality"
available_tools = ["read_file", "list_dir", "bash"]
max_iterations = 20
max_revisits = 3
# Lets the run end here (via "DONE") when the review approves the work,
# instead of being forced down the only declared edge back to implement.
transition_prompt = """
Review complete. Based on your findings:
- If there are issues that need fixing, respond with: implement
- If the code is clean and correct, respond with: DONE

Minor style issues don't warrant another implementation pass.
"""
system_prompt = """
You are the review stage. The implementation is complete.

Use the `changelog` region to see exactly which files changed (the
`implementation` region holds the mechanical record of every write the framework
saw, if the changelog looks incomplete), and `decisions` to understand why choices
were made before you question them. Read those files and provide a quality review:
You review here; you do not repair. There is no write or edit tool in this
stage and calling one is refused - a reviewer who fixes what they find is the
same person marking their own work. Report the problem and route back to
`implement`, which is where changes are made and re-reviewed.

- Correctness: does it match the plan AND the original task in `task`?
- Edge cases: what inputs or conditions could cause failures?
- Code quality: clarity, naming, error handling, conventions.
- Security: any obvious vulnerabilities?

Re-run the tests with bash - use the commands in `workflow`, which the implement
stage was told to follow (output → `test_results`). Don't approve on an
unverified claim that tests pass. Then diff what you ran against `baseline`:
anything that passed there and fails now is a regression, and is grounds for
NEEDS CHANGES on its own. Hold the work to `workflow`'s DONE WHEN line, and say
so if the implement stage skipped its own stated workflow (no baseline captured,
verification never run).

If `error_report` says the implement stage hit its iteration cap, it was cut off
before declaring the work done - assume the implementation is incomplete and
verify every DONE WHEN criterion rather than sampling.

End with one of:
  APPROVED - no significant issues
  NEEDS CHANGES - <list specific required changes>
"""

[stages.review.tool_permissions]
read_file = "allow"
list_dir  = "allow"
bash      = "ask"

# Test re-runs persist to `test_results` (a pointer + preview stays in conversation).
[stages.review.tool_routing]
default_region = "conversation"
[stages.review.tool_routing.overrides]
read_file = "codebase"
list_dir  = "codebase"
bash      = "test_results"

[stages.review.transitions.summary]
hint = "The work is done and reviewed"

[stages.review.transitions.implement]
hint = "Issues found - needs another implementation pass"
transform = "custom"

[stages.review.transitions.implement.transform_config]
carry = ["task", "constraints", "architecture", "plan", "codebase", "changelog", "decisions", "discovery", "workflow", "baseline"]
compact = ["conversation"]
clear = ["scratch", "test_results"]
compact_prompt = "Summarize review findings as a numbered list of required fixes."

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

# ─── Stage 4b: Reassess ──────────────────────────────────────────────────────
# Reached ONLY via a `stuck` edge (issue #106) - invisible during normal flow,
# exactly like error_recovery. Deliberately has NO write tools: the point is to
# stop editing and start thinking. It reads the runtime's `stuck_report` (which
# threshold tripped and why), finds the wrong assumption, and corrects the plan
# before handing back to implement.
[stages.reassess]
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 = "devstral:24b" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Step back after no progress: diagnose the dead end and re-plan"
available_tools = ["read_file", "list_dir", "bash", "context_write", "context_append"]
max_iterations = 8
max_revisits = 2
system_prompt = """
You are NOT here to write code - you have no write tools on purpose. You were
pulled out of implementation because you stopped making progress. The
`stuck_report` region says which threshold tripped and why.

Work through this in order:
1. Re-read the ORIGINAL task in `task` and the user's `constraints`. State in
   one sentence what "done" means. Not what the plan says - what the task says.
   Check it against the DONE WHEN line in `workflow`.
2. Separate what you have VERIFIED (a command you ran and an output you saw)
   from what you ASSUMED. `test_results`, `changelog`, `decisions` and
   `implementation` are your evidence. The bug is almost always in the assumed
   column.
3. Find the wrong assumption. The most common one by far is that you have been
   editing the wrong file. Use list_dir/read_file and bash (grep) to confirm
   WHERE the behavior under test actually lives before changing anything else.
4. If an earlier version worked and a later edit broke it, say so explicitly and
   make reverting to that state step 1 of the new plan. Use bash (git diff /
   git status) to see everything this run has changed. A working fix you broke
   is worth more than a fresh idea.

Then rewrite the `plan` region (context_write, key "plan") with a corrected
numbered plan whose FIRST step is the smallest change you can verify with one
command, and which names explicitly what NOT to touch again. Preserve the user's
approved intent - correct the route, not the destination. Append one line to
`errors` naming the dead end so it is not retried.
"""

[stages.reassess.tool_permissions]
read_file = "allow"
list_dir  = "allow"
bash      = "ask"

# Diagnostic bash output (git diff, greps, test re-runs) persists to test_results.
[stages.reassess.tool_routing]
default_region = "conversation"
[stages.reassess.tool_routing.overrides]
read_file = "codebase"
list_dir  = "codebase"
bash      = "test_results"

[stages.reassess.transitions.implement]
hint = "Corrected plan in hand - retry implementation"
transform = "custom"
[stages.reassess.transitions.implement.transform_config]
carry = ["discovery", "workflow", "task", "constraints", "architecture", "plan", "codebase", "implementation", "prototypes", "changelog", "decisions", "errors", "stuck_report", "error_report"]
compact = ["conversation"]
clear = ["scratch", "test_results"]
compact_prompt = "Summarize the reassessment as three things: the wrong assumption, the corrected approach, and what must be reverted first."

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

[stages.reassess.transitions.summary]
condition = "dead_end"
# Taken only when every other way out is revisit-exhausted, so it is never a
# route the model can pick early. Without it the run errors as dead-ended and
# the work done so far is lost; with it the run still hands back an answer.

# ─── Stage 5: Error Recovery ────────────────────────────────────────────────
# Only reachable via error condition edges - invisible during normal flow.

[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 = "Diagnose and resolve errors encountered during implementation"
available_tools = ["read_file", "bash", "context_append"]
max_iterations = 10
max_revisits = 2
system_prompt = """
An error occurred during implementation. Your job:
1. Read the error text the runtime captured in the `error_report` region.
2. Diagnose the root cause.
3. Suggest a fix or workaround.

Append a one-line summary of the failure to the `errors` region
(context_append) so repeated failures become visible as a pattern. If you see
the SAME error recurring there, change approach rather than repeating the fix.

Once you've identified the issue, transition back to implement to retry.
"""

[stages.error_recovery.transitions.implement]
hint = "Error diagnosed and resolved, retry implementation"
transform = "compact"

[stages.error_recovery.transitions.summary]
condition = "dead_end"
# Taken only when every other way out is revisit-exhausted, so it is never a
# route the model can pick early. Without it the run errors as dead-ended and
# the work done so far is lost; with it the run still hands back an answer.

# ─── Compaction ──────────────────────────────────────────────────────────────

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

# ─── Context layout ──────────────────────────────────────────────────────────
# Budgets are a percentage of the model's context window (issue #100) with an
# absolute `max_tokens`/`threshold_tokens` guard-rail. Percentages are ceilings.

# ─── 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 changed"
max_iterations = 8
system_prompt = """
Say what you changed, for whoever asked for it.

Lead with one sentence on what the change does. Then the files you touched and
what each one does now, one line each. Then anything they need to know before
merging: a decision you made, a test you could not run, a loose end you left.

Do not paste the code back. They can read the diff; what they cannot read is
why you did it this way.
"""

[stages.summary.transitions]

[context.regions]
# ── Inputs (pinned, stable, cacheable) ──
task         = { kind = "pinned", budget = "2%", max_tokens = 4000, required = true, required_message = "Describe the task via --task (or the API/ACP task field)." }
# Optional caller limits: --constraints "must stay on Node 18, no new deps".
constraints  = { kind = "pinned", budget = "1%", max_tokens = 2000, seed = "constraints" }
# Pre-loaded on startup: architecture / design docs (missing files skipped).
# Pre-loaded on startup: coding style, lint rules, contribution guide (missing files skipped).
conventions  = { kind = "pinned", budget = "2%", max_tokens = 3000, seed = { files = ["CONVENTIONS.md", "CONTRIBUTING.md", "STYLEGUIDE.md", "STYLE.md", ".editorconfig", "rustfmt.toml", ".rustfmt.toml", ".prettierrc", ".eslintrc.json", "ruff.toml", "pyproject.toml"] } }
architecture = { kind = "pinned", budget = "3%", max_tokens = 6000, seed = { files = ["ARCHITECTURE.md", "DESIGN.md", "docs/ARCHITECTURE.md", "docs/architecture.md", "README.md"] } }
plan         = { kind = "pinned", budget = "5%", max_tokens = 6000 }
# Verified findings from a `prototype` spike: the assumption, the verdict, and
# the command that settled it - including what was RULED OUT. Pinned so neither
# implement nor reassess redoes the spike.
prototypes   = { kind = "pinned", budget = "4%", max_tokens = 6000 }
# Written by the RUNTIME when a `stuck` edge fires (issue #106): which threshold
# tripped and why. Pinned so it survives the edge transform into `reassess`.
stuck_report = { kind = "pinned", budget = "1%", max_tokens = 2000 }
# 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 `error_recovery` or `review`.
error_report = { kind = "pinned", budget = "1%", max_tokens = 2000 }
# Deterministic repo scan, run once at spawn - the discover stage starts from
# facts instead of burning iterations on `ls`. `git ls-files` behaves identically
# on POSIX and Windows shells; outside a git repo it fails and this is simply
# left empty (non-fatal), and oversized output is trimmed to the budget.
# Refuse it with `--no-seed-commands` or `[security] allow_seed_commands = false`.
repo_files   = { kind = "pinned", budget = "3%", max_tokens = 4000, seed = { command = "git ls-files" } }

# ── Discovery (issue #108): written by the discover stage, read by every later
# stage. Pinned, so no edge transform can clear or compact them. `required` puts
# the runtime's own gate behind them (`require_context_regions`): the discover
# stage is re-run with a nudge until it actually fills them, so the synthesized
# workflow is a commitment the review stage can hold the run to, not a suggestion.
discovery    = { kind = "pinned", budget = "4%", max_tokens = 6000, required = true, required_message = "Populate `discovery` (context_write) with this project's build system, test runner, layout and conventions before leaving the discover stage." }
workflow     = { kind = "pinned", budget = "2%", max_tokens = 3000, required = true, required_message = "Populate `workflow` (context_write) with the tier and the literal BASELINE / VERIFY / DONE WHEN lines before leaving the discover stage." }
# Pre-change test state, captured by implement before its first edit. Without it
# a regression is indistinguishable from a pre-existing failure.
baseline     = { kind = "pinned", budget = "3%", max_tokens = 4000 }

# ── Knowledge (non-volatile; compacts to *_history) ──
codebase         = { kind = "compacting",      budget = "25%", compact_at = "80%", threshold_tokens = 25000, max_tokens = 40000 }
codebase_history = { kind = "compact_history", source_region = "codebase",      budget = "2%", max_tokens = 8000 }
implementation   = { kind = "compacting",      budget = "35%", compact_at = "80%", threshold_tokens = 32000, max_tokens = 40000 }
impl_history     = { kind = "compact_history", source_region = "implementation", budget = "2%", max_tokens = 8000 }

# ── Working records (sliding windows) + test feedback ──
# decisions: WHY choices were made. changelog: which files changed.
decisions    = { kind = "sliding_window", max_items = 10, budget = "3%", max_tokens = 4000 }
changelog    = { kind = "sliding_window", max_items = 20, budget = "2%", max_tokens = 3000 }
test_results = { kind = "clearable",      budget = "4%", max_tokens = 5000 }
# A small sliding window so a repeating failure is visible as a pattern rather
# than a one-off. Written explicitly via context_append by error_recovery and
# reassess - never by tool routing (only `conversation` may hold routed tool
# results; a second sliding_window desyncs them from their tool_use → API 400).
errors       = { kind = "sliding_window", max_items = 5, budget = "2%", max_tokens = 3000 }

# ── Conversation (bulk eviction for caching) + working memory ──
conversation = { kind = "sliding_window", max_items = 40, budget = "20%", max_tokens = 30000, strategy = "bulk", overflow = 20 }
scratch      = { kind = "clearable",      budget = "8%", max_tokens = 10000 }