pub struct AxPlaybook<S: AxAIClient, T: AxAIClient> { /* private fields */ }Expand description
A live, evolving context playbook bound to a program. Mirrors the TypeScript
AxPlaybook: grow it offline from examples (evolve), keep it growing online
from live feedback (update), render it into the program context (apply_to),
and persist/restore it (to_json/load). The evolution engine (ACE) is hidden
behind this surface, just as optimize() hides GEPA.
The bound program runs with the student client; the real reflector/curator are
focused AxGen sub-programs driven by the teacher client. The program, the
clients, the sub-programs, and the last prediction are held behind Rc<RefCell>
so the generator/reflector/curator closures installed into AxACE can mutate
them without statically aliasing the engine that owns those closures.
Implementations§
Source§impl<S: AxAIClient + 'static, T: AxAIClient + 'static> AxPlaybook<S, T>
impl<S: AxAIClient + 'static, T: AxAIClient + 'static> AxPlaybook<S, T>
pub fn new( program: AxGen, student: Rc<RefCell<S>>, teacher: Option<Rc<RefCell<T>>>, options: Value, ) -> Self
Build a playbook that SHARES an already-Rc<RefCell>-wrapped program. Used by
AxAgent::playbook() so the playbook injects its composed instruction straight
into the live pipeline stage instead of a private clone.
Sourcepub fn evolve(
&mut self,
examples: &[Value],
metric_fn: &mut dyn FnMut(&Value) -> Value,
options: &Value,
) -> AxResult<Value>
pub fn evolve( &mut self, examples: &[Value], metric_fn: &mut dyn FnMut(&Value) -> Value, options: &Value, ) -> AxResult<Value>
Grow the playbook offline from labeled examples, scoring each rollout with
metric_fn ({“prediction”, “example”}), then render the result into the bound
program. Returns {bestScore, playbook}.
Examples found in repository?
32fn main() -> AxResult<()> {
33 let mut program = ax("question:string -> answer:string")?;
34 program.set_instruction("Answer the question.");
35
36 let student = Rc::new(RefCell::new(ScriptedClient));
37 let mut pb = playbook(
38 program,
39 student,
40 None::<Rc<RefCell<ScriptedClient>>>,
41 json!({"maxEpochs": 1}),
42 );
43
44 let mut metric = |args: &Value| -> Value {
45 let answer = args
46 .get("prediction")
47 .and_then(|p| p.get("answer"))
48 .and_then(Value::as_str)
49 .unwrap_or("");
50 if answer.is_empty() {
51 json!(0.0)
52 } else {
53 json!(1.0)
54 }
55 };
56
57 let examples = vec![
58 json!({"question": "What is Ax?"}),
59 json!({"question": "Why typed signatures?"}),
60 ];
61 let result = pb.evolve(&examples, &mut metric, &json!({}))?;
62 let rendered = pb.render();
63 let state = pb.to_json();
64 assert!(
65 result.get("bestScore").is_some(),
66 "missing bestScore: {result}"
67 );
68 assert!(state.get("playbook").is_some(), "missing playbook: {state}");
69 assert!(state.get("artifact").is_some(), "missing artifact: {state}");
70 println!("rendered: {rendered}");
71 println!("rust-ace-playbook-ok");
72 Ok(())
73}Sourcepub fn evolve_agent<C: AxAIClient>(
&mut self,
agent: &mut AxAgent,
client: &mut C,
dataset: &Value,
options: &Value,
) -> AxResult<Value>
pub fn evolve_agent<C: AxAIClient>( &mut self, agent: &mut AxAgent, client: &mut C, dataset: &Value, options: &Value, ) -> AxResult<Value>
Verified agent-layer playbook learning from train/validation task sets. The agent is passed explicitly because Rust cannot safely store a self-reference inside the stage-bound playbook handle.
Examples found in repository?
75fn main() -> AxResult<()> {
76 // agent.playbook() binds an evolving context playbook to an agent stage. The
77 // "responder" target grows the user-facing answer stage; ACE remains an
78 // implementation detail behind playbook(), just as optimize() hides GEPA.
79 let mut agent = agent_with_options(
80 "question:string -> answer:string",
81 json!({"name": "qa", "description": "Answer the question.", "runtime": {"language": "Python"}}),
82 )?
83 .with_runtime(Box::new(Runtime))?;
84
85 let student = Rc::new(RefCell::new(ScriptedClient));
86 let mut pb = agent.playbook(
87 student,
88 None::<Rc<RefCell<ScriptedClient>>>,
89 json!({"target": "responder", "maxEpochs": 1}),
90 )?;
91
92 let dataset = json!({"train": [{"input": {"question": "Answer briefly."}, "score": 0}]});
93 let mut eval_client = ScriptedClient;
94
95 // A zero minimum gain exercises verified acceptance. A positive minimum gain
96 // rejects the same flat score and must restore the exact pre-proposal snapshot.
97 let accepted = pb.evolve_agent(
98 &mut agent,
99 &mut eval_client,
100 &dataset,
101 &json!({"verify": true, "minHeldInGain": 0, "maxProposals": 1, "maxMetricCalls": 2}),
102 )?;
103 let before_rejection = serde_json::to_string(&pb.to_json())?;
104 let rejected = pb.evolve_agent(
105 &mut agent,
106 &mut eval_client,
107 &dataset,
108 &json!({"verify": true, "minHeldInGain": 0.1, "maxProposals": 1, "maxMetricCalls": 2}),
109 )?;
110 let after_rejection = serde_json::to_string(&pb.to_json())?;
111
112 assert_eq!(
113 accepted["metricCallsUsed"].as_u64(),
114 Some(2),
115 "bad metric budget: {accepted}"
116 );
117 assert_eq!(
118 accepted["outcomes"][0]["accepted"].as_bool(),
119 Some(true),
120 "verified acceptance failed: {accepted}"
121 );
122 assert_eq!(
123 rejected["metricCallsUsed"].as_u64(),
124 Some(2),
125 "bad metric budget: {rejected}"
126 );
127 assert_eq!(
128 rejected["outcomes"][0]["accepted"].as_bool(),
129 Some(false),
130 "verified rejection failed: {rejected}"
131 );
132 assert_eq!(
133 after_rejection, before_rejection,
134 "rejected proposal was not rolled back exactly"
135 );
136 assert!(
137 pb.to_json().get("playbook").is_some(),
138 "missing playbook: {}",
139 pb.to_json()
140 );
141 println!("accepted: {}", accepted["outcomes"][0]);
142 println!("rejected: {}", rejected["outcomes"][0]);
143 println!("rust-agent-playbook-ok");
144 Ok(())
145}Sourcepub fn update(&mut self, args: &Value) -> AxResult<Value>
pub fn update(&mut self, args: &Value) -> AxResult<Value>
Refine the playbook online from a single live interaction. Safe to call
without a prior evolve/load; the engine is hydrated lazily on first use.
Sourcepub fn apply_to(&mut self, program: Option<&mut AxGen>)
pub fn apply_to(&mut self, program: Option<&mut AxGen>)
Render the current playbook into a program context (defaults to the bound program). When a different program is passed, its instruction is composed with the rendered playbook in place.
Sourcepub fn render(&self) -> String
pub fn render(&self) -> String
Return the current playbook as a markdown block.
Examples found in repository?
32fn main() -> AxResult<()> {
33 let mut program = ax("question:string -> answer:string")?;
34 program.set_instruction("Answer the question.");
35
36 let student = Rc::new(RefCell::new(ScriptedClient));
37 let mut pb = playbook(
38 program,
39 student,
40 None::<Rc<RefCell<ScriptedClient>>>,
41 json!({"maxEpochs": 1}),
42 );
43
44 let mut metric = |args: &Value| -> Value {
45 let answer = args
46 .get("prediction")
47 .and_then(|p| p.get("answer"))
48 .and_then(Value::as_str)
49 .unwrap_or("");
50 if answer.is_empty() {
51 json!(0.0)
52 } else {
53 json!(1.0)
54 }
55 };
56
57 let examples = vec![
58 json!({"question": "What is Ax?"}),
59 json!({"question": "Why typed signatures?"}),
60 ];
61 let result = pb.evolve(&examples, &mut metric, &json!({}))?;
62 let rendered = pb.render();
63 let state = pb.to_json();
64 assert!(
65 result.get("bestScore").is_some(),
66 "missing bestScore: {result}"
67 );
68 assert!(state.get("playbook").is_some(), "missing playbook: {state}");
69 assert!(state.get("artifact").is_some(), "missing artifact: {state}");
70 println!("rendered: {rendered}");
71 println!("rust-ace-playbook-ok");
72 Ok(())
73}Sourcepub fn get_state(&self) -> Value
pub fn get_state(&self) -> Value
Return a serializable snapshot of the playbook and its history.
Sourcepub fn to_json(&self) -> Value
pub fn to_json(&self) -> Value
Alias for get_state.
Examples found in repository?
32fn main() -> AxResult<()> {
33 let mut program = ax("question:string -> answer:string")?;
34 program.set_instruction("Answer the question.");
35
36 let student = Rc::new(RefCell::new(ScriptedClient));
37 let mut pb = playbook(
38 program,
39 student,
40 None::<Rc<RefCell<ScriptedClient>>>,
41 json!({"maxEpochs": 1}),
42 );
43
44 let mut metric = |args: &Value| -> Value {
45 let answer = args
46 .get("prediction")
47 .and_then(|p| p.get("answer"))
48 .and_then(Value::as_str)
49 .unwrap_or("");
50 if answer.is_empty() {
51 json!(0.0)
52 } else {
53 json!(1.0)
54 }
55 };
56
57 let examples = vec![
58 json!({"question": "What is Ax?"}),
59 json!({"question": "Why typed signatures?"}),
60 ];
61 let result = pb.evolve(&examples, &mut metric, &json!({}))?;
62 let rendered = pb.render();
63 let state = pb.to_json();
64 assert!(
65 result.get("bestScore").is_some(),
66 "missing bestScore: {result}"
67 );
68 assert!(state.get("playbook").is_some(), "missing playbook: {state}");
69 assert!(state.get("artifact").is_some(), "missing artifact: {state}");
70 println!("rendered: {rendered}");
71 println!("rust-ace-playbook-ok");
72 Ok(())
73}More examples
75fn main() -> AxResult<()> {
76 // agent.playbook() binds an evolving context playbook to an agent stage. The
77 // "responder" target grows the user-facing answer stage; ACE remains an
78 // implementation detail behind playbook(), just as optimize() hides GEPA.
79 let mut agent = agent_with_options(
80 "question:string -> answer:string",
81 json!({"name": "qa", "description": "Answer the question.", "runtime": {"language": "Python"}}),
82 )?
83 .with_runtime(Box::new(Runtime))?;
84
85 let student = Rc::new(RefCell::new(ScriptedClient));
86 let mut pb = agent.playbook(
87 student,
88 None::<Rc<RefCell<ScriptedClient>>>,
89 json!({"target": "responder", "maxEpochs": 1}),
90 )?;
91
92 let dataset = json!({"train": [{"input": {"question": "Answer briefly."}, "score": 0}]});
93 let mut eval_client = ScriptedClient;
94
95 // A zero minimum gain exercises verified acceptance. A positive minimum gain
96 // rejects the same flat score and must restore the exact pre-proposal snapshot.
97 let accepted = pb.evolve_agent(
98 &mut agent,
99 &mut eval_client,
100 &dataset,
101 &json!({"verify": true, "minHeldInGain": 0, "maxProposals": 1, "maxMetricCalls": 2}),
102 )?;
103 let before_rejection = serde_json::to_string(&pb.to_json())?;
104 let rejected = pb.evolve_agent(
105 &mut agent,
106 &mut eval_client,
107 &dataset,
108 &json!({"verify": true, "minHeldInGain": 0.1, "maxProposals": 1, "maxMetricCalls": 2}),
109 )?;
110 let after_rejection = serde_json::to_string(&pb.to_json())?;
111
112 assert_eq!(
113 accepted["metricCallsUsed"].as_u64(),
114 Some(2),
115 "bad metric budget: {accepted}"
116 );
117 assert_eq!(
118 accepted["outcomes"][0]["accepted"].as_bool(),
119 Some(true),
120 "verified acceptance failed: {accepted}"
121 );
122 assert_eq!(
123 rejected["metricCallsUsed"].as_u64(),
124 Some(2),
125 "bad metric budget: {rejected}"
126 );
127 assert_eq!(
128 rejected["outcomes"][0]["accepted"].as_bool(),
129 Some(false),
130 "verified rejection failed: {rejected}"
131 );
132 assert_eq!(
133 after_rejection, before_rejection,
134 "rejected proposal was not rolled back exactly"
135 );
136 assert!(
137 pb.to_json().get("playbook").is_some(),
138 "missing playbook: {}",
139 pb.to_json()
140 );
141 println!("accepted: {}", accepted["outcomes"][0]);
142 println!("rejected: {}", rejected["outcomes"][0]);
143 println!("rust-agent-playbook-ok");
144 Ok(())
145}Sourcepub fn load(&mut self, snapshot: &Value) -> &mut Self
pub fn load(&mut self, snapshot: &Value) -> &mut Self
Restore a snapshot into this handle and render it into the bound program.
Sourcepub fn configure_auto(&mut self, level: &str)
pub fn configure_auto(&mut self, level: &str)
Set the evolution intensity preset ("light", "medium", or "heavy").
Sourcepub fn set_apply_hook(&mut self, hook: Box<dyn FnMut(&str)>)
pub fn set_apply_hook(&mut self, hook: Box<dyn FnMut(&str)>)
Redirect playbook injection. Used to push the rendered playbook into a pipeline stage instead of the bound program.