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
//! Conditional edges and bounded cycles — what turns the DAG into a graph.
//!
//! A DAG says what may run in parallel. It cannot say *"if the review fails, go
//! back and revise"*, and that shape is most of agent work: draft → review →
//! revise → review again. Without it a caller either unrolls the loop by hand
//! (three copies of the same job, and still a guess at how many) or drops out of
//! the orchestrator entirely.
//!
//! One concept covers both gaps. A [`Router`] runs after a job succeeds and says
//! what happens next: carry on, jump back to an earlier job, or stop the run.
//! The `deps` graph stays acyclic — cycle detection at load time keeps working —
//! and re-entry is a scheduling decision, made once per completion, with a hard
//! visit cap so a loop that never converges ends as a dead letter rather than a
//! bill.
//!
//! Put the criterion in the router, not in the prompt. A model asked to judge
//! its own output against a rule tends to agree with itself — measured on a real
//! run, one replied "LGTM" for a 30-character answer against a limit of 15, and
//! the loop never ran. The same check as code is exact, and it costs no tokens.
//!
//! ```ignore
//! let orch = Orchestrator::new(runner)
//! .route("review", |r: &JobResult| {
//! let n = r.text.chars().count();
//! if n <= LIMIT {
//! Next::Continue
//! } else {
//! Next::back_to_with("revise", format!("{n} characters, limit is {LIMIT}"))
//! }
//! })
//! .with_max_visits(4);
//! ```
use crate;
/// What happens after a job succeeds.
/// Decides [`Next`] from a finished job's result.
///
/// Implemented for any `Fn(&JobResult) -> Next`, so a closure is a router and
/// nothing else has to be written for the common case.