leviath-cli 0.1.2

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
[agent]
name = "coder"
version = "0.0.1"
description = "Multi-stage coding agent - discover, analyze, optionally prototype, implement, and review, with stuck detection and graph-based error recovery"
entry_stage = "discover"

# Model selection is an ordered fallback list per stage: the first provider
# configured at runtime wins. Anthropic first, then OpenAI, then a local Ollama
# model as a last resort. `allow_user_default` (on by default) still lets the
# user's configured default model catch anything unlisted.

# Global tool permissions: write/exec require approval unless overridden.
[tool_permissions]
read_file  = "allow"
list_dir   = "allow"
write_file = "ask"
edit_file  = "ask"
bash       = "ask"

# ─── Stage 1: Discover ────────────────────────────────────────────────────────
# Orient before acting: map the codebase and synthesize the verification
# workflow the later stages must follow. Cheap model, hard iteration cap, and a
# single non-error edge so the runtime auto-follows it without a routing call.
[stages.discover]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { 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 code is written, answer two questions about THIS repository:
what is it, and how do I verify my 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).
- `conventions` and `architecture` are pre-loaded with the project's style/lint
  rules and design docs.
- 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.

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 implement stage must
  BUILD its own verification: name the smoke test or assertion it should 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 the implement stage should fill with
  a new test.
- 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.analyze]
hint = "Codebase mapped and verification workflow synthesized"
transform = "direct"

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

# ─── Stage 2: Analyze ─────────────────────────────────────────────────────────
[stages.analyze]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "ollama", model = "qwen3.5:9b" }] }
description = "Understand requirements and plan the implementation"
available_tools = ["read_file", "list_dir"]
max_iterations = 15
# The prototype back-edge makes analyze re-enterable, so it needs a revisit cap.
max_revisits = 2
# Two Always edges out of analyze put it in the LLM-choice lane, so this prompt
# is what decides whether a task detours through `prototype`. Most tasks should
# not - the spike is for when the APPROACH is genuinely unknown.
transition_prompt = """
Plan ready. Choose where to go next:
- Respond with `prototype` when the APPROACH is uncertain - you are not sure the
  API/library/algorithm behaves the way the plan assumes, or you could not pin
  down where the behavior you need to change actually lives. A ten-line spike
  settles that far more cheaply than discovering it forty edits into `implement`.
- Respond with `implement` when the plan is a straightforward application of a
  pattern that already exists in this codebase, or `discovery` already told you
  where the change belongs.

Prototyping is not free - only take it when you expect to learn something that
would change what you write.
"""
system_prompt = """
You are analyzing a coding task to produce a concise implementation plan.

Start by categorizing the task - it drives everything downstream:
- NEW FEATURE: what to build, where it slots in, which existing patterns to follow.
- BUGFIX: reproduce first, isolate the root cause, then scope the minimal fix.
- REFACTOR: preserve behavior; identify the seam and what must stay invariant.

Then:
1. Read `discovery` and `workflow` first - the discover stage already mapped this
   codebase and chose how the work will be verified. Do NOT re-explore what they
   already answer; build on them.
2. Check `conventions` and `architecture` - pre-loaded style/lint rules and design
   docs. Follow them; don't rediscover them.
3. Estimate scope: which files to create/modify, and roughly how large the change is.
   Name them - do not create them. This stage has no tool that writes or edits a
   file, and calling one is refused; `implement` makes the change you describe.
4. Identify dependencies - modules, libraries, config, or tests this touches.
   Use list_dir/read_file ONLY to fill real gaps left by `discovery`. For a fresh
   task with no existing code, skip exploration entirely.

Output a short bullet-list plan: which files to create/modify, what each does, key
decisions, and the dependencies you found. If `workflow` is TIER 1, the plan must
include writing the verification the implement stage will run. Be brief - the
implement stage does the actual coding and reads this plan from the conversation.
"""

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

[stages.analyze.transitions.implement]
hint = "The fix location and approach are clear - begin implementation"
transform = "direct"

[stages.analyze.transitions.prototype]
hint = "The approach is uncertain - spike it before committing to a full implementation"
transform = "direct"

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

# ─── Stage 2b: Prototype (elective) ───────────────────────────────────────────
# NOT a mandatory hop - `analyze` only routes here when it judges the approach
# uncertain. The point is to buy information cheaply: prove or kill the riskiest
# assumption, write down what was ruled out, and REPLACE the plan with one that
# rests on something that actually ran. `implement` then executes evidence
# rather than a guess.
[stages.prototype]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-sonnet-5" }, { provider = "openai", model = "gpt-5.4-mini" }, { provider = "ollama", model = "devstral:24b" }] }
description = "Spike the riskiest assumption in the plan before committing to 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 `analyze` if every hypothesis failed. The plan rests on a wrong
  premise, so it needs rewriting rather than executing; what you ruled out is in
  `prototypes` and will inform the new plan.
"""
system_prompt = """
You are spiking, not implementing. Pick the ONE assumption in the plan 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") so `implement` executes what you learned.

Keep this to a handful of iterations. You are buying information, not shipping
code - do not start the real implementation here, and throw away scaffolding you
created purely to test a hypothesis.
"""

[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.analyze]
hint = "Every hypothesis failed - the plan's premise is wrong, re-plan"
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"

# ─── Stage 3: Implement ───────────────────────────────────────────────────────
[stages.implement]
mode = "autonomous"
model = { models = [{ provider = "anthropic", model = "claude-opus-5" }, { provider = "openai", model = "gpt-5.5" }, { provider = "ollama", model = "devstral:24b" }] }
description = "Write code according to the plan, verifying continuously"
available_tools = ["write_file", "read_file", "edit_file", "list_dir", "bash", "context_write", "ask_user_confirm"]
max_iterations = 50
max_revisits = 3
# ask_user_confirm suspends the run until a person answers, which is deliberate
# here: this stage checks in before doing something it cannot undo. Declared so
# `lev validate` reports it as a choice rather than an oversight.
allow_blocking_tools = true
system_prompt = """
You are implementing the plan from the `plan` region. Write working,
production-quality code that matches the project's `conventions`.

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 with bash 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. Implement incrementally - one coherent file/change at a time, not one giant
   dump. Use write_file to create files and edit_file to modify existing ones.
   Do NOT edit files through bash - no `sed -i`, no `tee`, no `>`/`>>`
   redirection, no here-docs. Bash is for running tests and builds and for
   read-only exploration. Only write_file/edit_file are recorded in the
   `implementation` region the reviewer reads, and this stage will not hand off
   to review until at least one of them has landed.
   Handle errors explicitly (validate inputs, propagate/return errors, no silent
   swallowing) - match the error-handling style already in this codebase.
   Write or update tests alongside the code when the task warrants it.
3. VERIFY after each logical change, not just at the end. Run the VERIFY command
   with bash; output is routed to `test_results`.
4. 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. Never move
   on from a regression.
5. 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 and do not
report success you haven't observed.

If a test keeps failing, check the `errors` region - it keeps the recent failures
so you can spot a repeating pattern instead of chasing the same wall.

Do NOT spend iterations re-reading the codebase unless you need something
specific; `discovery` already has the map.

If you hit a destructive or hard-to-reverse action you're unsure about, use
ask_user_confirm before proceeding. Otherwise work autonomously.

Finish with a short summary of what changed, 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) and the full output as text in the region, so the
# model still sees each action landed.
#
# write_file/edit_file confirmations persist in `implementation`, which makes it
# the run's changelog: it is what the review stage is told to read, it survives
# `conversation` eviction, and - being persisted context - it also satisfies the
# transition gate 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"

# The gate refuses this edge until the stage has actually written something, so an
# agent that explored the codebase through bash and changed nothing gets sent back
# instead of handing an untouched workspace to review (issue #107).
[stages.implement.transitions.review]
hint = "Implementation complete, ready for review"
transform = "compact"
gate = { require_modifications = true, region = "implementation" }

# Runtime escape hatch (issue #106). This is the fix for the observed failure
# mode: ~100 iterations spent editing the wrong file, a working fix broken and
# never recovered, with no exception ever raised for `error_recovery` to catch.
# `stuck_after_same_file_edits` is the trigger that actually catches it; the
# other two are backstops. Bounded by reassess's max_revisits.
[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`, which is the raw evidence
# reassess most needs. Compact only the conversation and leave the rest intact.
[stages.implement.transitions.reassess.transform_config]
carry = ["discovery", "workflow", "architecture", "conventions", "task", "plan", "codebase", "implementation", "prototypes", "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"

# ─── Stage 4: Review ──────────────────────────────────────────────────────────
# Interactive: the user sees the review before it decides. allow_complete lets
# the reviewer end the run (DONE) when the code is clean instead of being forced
# back into another implementation pass by a lone outgoing edge.
[stages.review]
mode = "interactive"
model = { models = [{ provider = "anthropic", model = "claude-opus-5" }, { provider = "openai", model = "gpt-5.5" }, { provider = "ollama", model = "devstral:24b" }] }
description = "Review the code before finalizing"
available_tools = ["read_file", "list_dir", "bash"]
max_iterations = 10
max_revisits = 3
allow_complete = true
transition_prompt = """
Review complete. Based on your findings:
- If there are issues that must be fixed, respond with: implement
- If the code is clean and correct, respond with: DONE

Minor style nits don't warrant another implementation pass.
"""
system_prompt = """
You are reviewing the implementation. Verify it against the ORIGINAL task in the
`task` region - not just against the plan (the plan can be wrong).

You review here; you do not repair. This stage has no write or edit tool and
calling one is refused - report what you find and route back to `implement`,
which is where changes are made and re-reviewed.

1. Read the files that were created or modified (they are tracked in the
   `implementation` region; read_file for anything you need to confirm).
2. Re-run the tests with bash - use the commands in `workflow`, which the
   implement stage was told to follow. Output goes to `test_results`. Don't
   approve on unverified claims that "tests pass".
3. Diff what you just ran against `baseline`: anything that passed there and
   fails now is a regression the implement stage was required to fix, and is
   grounds for NEEDS CHANGES on its own.
4. Hold the work to `workflow`'s DONE WHEN line, not to your own impression of
   "close enough". If the implement stage skipped its own stated workflow (no
   baseline captured, verification never run), say so.
5. Check: correctness vs. the task, edge cases, error handling, security, and
   code quality/conventions.

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 - <numbered list of specific required fixes>
"""

[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"

# Custom transform: carry the plan/architecture/task/codebase forward, compact the
# review conversation into a fix list, and clear scratch + test_results before
# re-implementing so stale failures don't confuse the next pass.
[stages.review.transitions.implement]
hint = "Issues found - needs another implementation pass"
transform = "custom"
[stages.review.transitions.implement.transform_config]
carry = ["architecture", "conventions", "task", "plan", "codebase", "discovery", "workflow", "baseline"]
compact = ["conversation"]
clear = ["scratch", "test_results"]
compact_prompt = "Summarize the 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 whole 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 REPLACES
# 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 = "ollama", model = "devstral:24b" }] }
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`. 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`, `errors` and `implementation` are your
   evidence. Be honest: 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 REPLACE 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. 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", "architecture", "conventions", "task", "plan", "codebase", "implementation", "prototypes", "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"

# ─── Stage 5: Error recovery ──────────────────────────────────────────────────
# Only reachable via error 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 = "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. The `error_report` region holds the error text the runtime
captured - read it first, diagnose the root cause, and note 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, then transition back to
implement to retry. If you see the SAME error recurring in `errors`, change
approach rather than repeating the fix.
"""

# Route tool chatter to scratch (clearable), not `errors`: `errors` is a
# sliding_window and only `conversation` may hold routed ToolResult blocks
# (a second sliding_window desyncs the result from its tool_use → API 400).
# The agent still records failures to `errors` explicitly via context_append.
[stages.error_recovery.tool_routing]
default_region = "conversation"
[stages.error_recovery.tool_routing.overrides]
read_file = "codebase"

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

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

# ─── Context layout ───────────────────────────────────────────────────────────
# Budgets are given as a percentage of the model's context window (issue #100),
# with an absolute `max_tokens`/`threshold_tokens` guard-rail so the layout is
# sensible on any model. Percentages are ceilings, not reservations - they may
# sum past 100% since regions rarely fill at once.
[context.regions]
# ── Inputs (pinned, stable, cacheable) ──
task         = { kind = "pinned", budget = "2%", max_tokens = 4000, required = true, required_message = "Describe the coding task via --task (or the API/ACP task field)." }
# 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" } }
# 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"] } }
# Pre-loaded on startup: architecture / design docs (missing files skipped).
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 = "3%", max_tokens = 4000 }
# 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 }

# ── 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, survives message eviction; 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 }

# ── Feedback loops ──
# test_results is clearable so stale failures are wiped between retry passes.
test_results = { kind = "clearable",      budget = "4%", max_tokens = 5000 }
# errors is a small sliding window so a repeating failure is visible as a pattern.
errors       = { kind = "sliding_window", max_items = 5, budget = "2%", max_tokens = 3000 }

# ── Conversation (bulk eviction for prompt 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 }