Skip to main content

harness_loop/orchestrator/
planner.rs

1//! `Planner` — produces (and re-produces) the DAG.
2//!
3//! The same interface serves initial planning and **dynamic replanning**:
4//! the orchestrator calls `plan` with the goal and everything succeeded so
5//! far, and the planner returns either more Jobs to merge or `Done`. This is
6//! the feedback edge that turns a static "plan-then-execute" workflow into an
7//! agent that adapts its plan to what it learns mid-run.
8
9use crate::orchestrator::dag::PlanDelta;
10use crate::orchestrator::job::{Job, JobId, JobResult};
11use async_trait::async_trait;
12
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum PlannerError {
16    #[error("planner: {0}")]
17    Plan(String),
18}
19
20/// Decides what Jobs the Run should contain next.
21///
22/// `?Send`: a planner may itself drive a sub-agent, whose future is not
23/// `Send`; the orchestrator runs everything on one thread.
24#[async_trait(?Send)]
25pub trait Planner {
26    async fn plan(
27        &self,
28        goal: &str,
29        succeeded: &[(JobId, JobResult)],
30    ) -> Result<PlanDelta, PlannerError>;
31}
32
33/// A planner that emits a fixed set of Jobs on the first call and `Done`
34/// thereafter. Use it to seed a Run from a precomputed plan while still going
35/// through the planner interface (so adding real replanning later is a
36/// drop-in swap).
37pub struct StaticPlanner {
38    jobs: std::sync::Mutex<Option<Vec<Job>>>,
39}
40
41impl StaticPlanner {
42    pub fn new(jobs: impl IntoIterator<Item = Job>) -> Self {
43        Self {
44            jobs: std::sync::Mutex::new(Some(jobs.into_iter().collect())),
45        }
46    }
47}
48
49#[async_trait(?Send)]
50impl Planner for StaticPlanner {
51    async fn plan(
52        &self,
53        _goal: &str,
54        _succeeded: &[(JobId, JobResult)],
55    ) -> Result<PlanDelta, PlannerError> {
56        match self.jobs.lock().unwrap().take() {
57            Some(jobs) => Ok(PlanDelta::Add(jobs)),
58            None => Ok(PlanDelta::Done),
59        }
60    }
61}