pub struct TaskGraph { /* private fields */ }Expand description
A directed acyclic graph (DAG) of TaskNodes.
§Invariants (enforced by add_task)
- Every node ID is unique.
- Every referenced dependency ID must already exist in the graph.
- No cycles — checked by running Kahn’s algorithm after every insert.
§Storage
Nodes are stored in a Vec (preserving insertion order) together with an
ID→index map for O(1) lookups. The dependency lists are the adjacency
representation; we build a reverse map on demand for algorithms that need it.
Implementations§
Source§impl TaskGraph
impl TaskGraph
Sourcepub fn add_task(&mut self, node: TaskNode) -> Result<()>
pub fn add_task(&mut self, node: TaskNode) -> Result<()>
Add a new task node to the graph.
§Errors
- Duplicate ID
- Unknown dependency ID (must be added before the dependent node)
- Cycle detection (added after all edges are registered)
Sourcepub fn mark_running(&mut self, id: &str) -> Result<()>
pub fn mark_running(&mut self, id: &str) -> Result<()>
Mark a task as Running.
§Errors
- Task ID not found
- Task is not in
Pendingstate (can only start from Pending)
Sourcepub fn mark_completed(&mut self, id: &str, result: AgentResult) -> Result<()>
pub fn mark_completed(&mut self, id: &str, result: AgentResult) -> Result<()>
Sourcepub fn mark_failed(&mut self, id: &str, error: String) -> Result<()>
pub fn mark_failed(&mut self, id: &str, error: String) -> Result<()>
Mark a task as Failed.
The retries counter in the Failed variant is initialised to 0 if the
task was previously Pending/Running, or incremented if it was already
Failed (i.e. this is a retry that also failed).
§Errors
- Task ID not found
Sourcepub fn mark_cancelled(&mut self, id: &str) -> Result<()>
pub fn mark_cancelled(&mut self, id: &str) -> Result<()>
Sourcepub fn reset_for_retry(&mut self, id: &str) -> Result<()>
pub fn reset_for_retry(&mut self, id: &str) -> Result<()>
Reset a failed task back to Pending so it can be retried.
The executor calls this before re-queueing a Failed task.
§Errors
- Task ID not found
- Task is not in
Failedstate
Sourcepub fn nodes(&self) -> impl Iterator<Item = &TaskNode>
pub fn nodes(&self) -> impl Iterator<Item = &TaskNode>
Returns an iterator over all nodes in insertion order.
Sourcepub fn get(&self, id: &str) -> Option<&TaskNode>
pub fn get(&self, id: &str) -> Option<&TaskNode>
Look up a node by ID. Returns None if not found.
Sourcepub fn topological_sort(&self) -> Result<Vec<String>>
pub fn topological_sort(&self) -> Result<Vec<String>>
Topological sort using Kahn’s BFS algorithm.
Returns a linearised ordering of all task IDs such that every task
appears after all of its dependencies. If multiple orderings are valid
(i.e. there are independent tasks), the result is deterministic but
arbitrary — use compute_waves to get
explicit parallel batches.
§Errors
Returns an error if a cycle is detected (should not happen if nodes were
added via add_task, which validates on insert).
Sourcepub fn compute_waves(&self) -> Result<Vec<Vec<String>>>
pub fn compute_waves(&self) -> Result<Vec<Vec<String>>>
Compute parallel execution waves.
A “wave” is a group of tasks that:
- Have all dependencies in earlier waves (so they can run immediately once the previous wave finishes), AND
- Are independent of each other within the wave (no edge between them).
This is the critical function for “完全安全托管” — the executor uses these waves to maximise concurrency: it runs all tasks in wave 0 in parallel, waits for all to complete, then runs wave 1 in parallel, etc.
Algorithm: assign each node a “wave number” = 1 + max(wave numbers of its dependencies). Nodes with no dependencies get wave 0. This is a standard “longest path in a DAG” computation.
§Returns
A Vec<Vec<String>> where:
result[0]= IDs of tasks that can run immediately (no deps)result[1]= IDs of tasks whose deps are all inresult[0]- …and so on
§Errors
Propagates any cycle error from topological_sort.
Sourcepub fn next_ready(&self) -> Vec<&TaskNode>
pub fn next_ready(&self) -> Vec<&TaskNode>
Returns all tasks that are currently eligible to run.
A task is “ready” when:
- Its status is
Pending, AND - Every task in
depends_onhas statusCompleted.
The executor calls this after each task completes to find new tasks to dispatch.
Sourcepub fn is_finished(&self) -> bool
pub fn is_finished(&self) -> bool
Returns true when every node in the graph is in a terminal state.
The executor uses this as its loop termination condition.
Sourcepub fn is_all_completed(&self) -> bool
pub fn is_all_completed(&self) -> bool
Returns true when every node has status Completed.
Source§impl TaskGraph
impl TaskGraph
Sourcepub fn rebuild_index(&mut self)
pub fn rebuild_index(&mut self)
Rebuild the id_to_index map after deserialisation.
serde skips id_to_index (marked #[serde(skip)]) so we must
rebuild it when loading a saved graph. Call this immediately after
deserialising:
let mut graph: TaskGraph = serde_json::from_str(&json)?;
graph.rebuild_index();