Skip to main content

edit_loop/
edit_loop.rs

1use agent_line::{Agent, Ctx, Outcome, RetryHint, Runner, StepResult, Workflow};
2
3#[derive(Clone, Debug)]
4struct Doc {
5    text: String,
6    revision: u32,
7}
8
9struct Writer;
10impl Agent<Doc> for Writer {
11    fn name(&self) -> &'static str {
12        "writer"
13    }
14    fn run(&mut self, mut state: Doc, ctx: &mut Ctx) -> StepResult<Doc> {
15        state.revision += 1;
16        ctx.log(format!("writer: producing revision {}", state.revision));
17
18        // Simulate: first draft has typos, second is clean.
19        if state.revision == 1 {
20            state.text = "Hello wrold! This is a dcument.".to_string();
21        } else {
22            state.text = "Hello world! This is a document.".to_string();
23        }
24
25        Ok((state, Outcome::Continue))
26    }
27}
28
29struct Validator;
30impl Agent<Doc> for Validator {
31    fn name(&self) -> &'static str {
32        "validator"
33    }
34    fn run(&mut self, state: Doc, ctx: &mut Ctx) -> StepResult<Doc> {
35        let mut errors = Vec::new();
36
37        if state.text.contains("wrold") {
38            errors.push("typo: 'wrold' should be 'world'");
39        }
40        if state.text.contains("dcument") {
41            errors.push("typo: 'dcument' should be 'document'");
42        }
43
44        if errors.is_empty() {
45            ctx.log("validator: all checks passed");
46            Ok((state, Outcome::Done))
47        } else {
48            for e in &errors {
49                ctx.log(format!("validator: {e}"));
50            }
51            Ok((state, Outcome::Next("fixer")))
52        }
53    }
54}
55
56struct Fixer {
57    retried: bool,
58}
59
60impl Agent<Doc> for Fixer {
61    fn name(&self) -> &'static str {
62        "fixer"
63    }
64    fn run(&mut self, mut state: Doc, ctx: &mut Ctx) -> StepResult<Doc> {
65        // Collect logs first, then clear. Reading logs() borrows &self,
66        let entries: Vec<String> = ctx.logs().to_vec();
67
68        for entry in &entries {
69            if entry.contains("wrold") {
70                state.text = state.text.replace("wrold", "world");
71                ctx.log("fixer: corrected 'wrold' -> 'world'");
72            }
73            if entry.contains("dcument") {
74                state.text = state.text.replace("dcument", "document");
75                ctx.log("fixer: corrected 'dcument' -> 'document'");
76            }
77        }
78
79        if !self.retried {
80            self.retried = true;
81            ctx.log("fixer: retrying to double-check fixes");
82            Ok((state, Outcome::Retry(RetryHint::new("double-checking"))))
83        } else {
84            self.retried = false;
85            Ok((state, Outcome::Next("validator")))
86        }
87    }
88}
89
90fn main() {
91    let mut ctx = Ctx::new();
92
93    let mut runner = Runner::new(
94        Workflow::builder("edit-loop")
95            .register(Writer)
96            .register(Validator)
97            .register(Fixer { retried: false })
98            .start_at("writer")
99            .then("validator")
100            .build()
101            .unwrap(),
102    );
103
104    let mut revision = 0;
105    for round in 1..=3 {
106        println!("=== Round {round} ===");
107
108        let doc = Doc {
109            text: String::new(),
110            revision,
111        };
112
113        match runner.run(doc, &mut ctx) {
114            Ok(doc) => {
115                println!("  Final text: {:?}", doc.text);
116                println!("  Revisions:  {}", doc.revision);
117                revision = doc.revision;
118            }
119            Err(e) => println!("  Error: {e}"),
120        }
121
122        println!("  Log:");
123        for entry in ctx.logs() {
124            println!("    {entry}");
125        }
126        ctx.clear_logs();
127        println!();
128    }
129}