Your first task
You end this page with a Task<String, String> that turns a list of crate names into a one-line report. It has two task nodes, one transition between them and a finish. The workspace-architect showcase starts the same way, with a discover node, before it fans out to one agent per crate.
Dependencies
Section titled “Dependencies”The task needs no model, so the swiftide-tasks feature flag is enough. swiftide-agents also turns swiftide-tasks on.
cargo add swiftide@0.34 --features swiftide-taskscargo add tokio --features macros,rt-multi-threadcargo add anyhowProgram
Section titled “Program”main builds the task and runs it:
use swiftide::tasks::{NodeError, Task, TaskRunOutcome};
#[tokio::main]pub async fn main() -> anyhow::Result<()> { // A task from a manifest listing (one crate per line) to a one-line report. let mut task = Task::<String, String>::new();
let discover = task.register_node_fn(|listing: &String| { let crates: Vec<String> = listing.lines().map(str::trim).map(String::from).collect(); Ok::<_, NodeError>(crates) }); let report = task.register_node_fn(|crates: &Vec<String>| { Ok::<_, NodeError>(format!("{} crates: {}", crates.len(), crates.join(", "))) });
task.starts_with(discover); task.register_transition(discover, move |crates| report.transitions_with(crates))?; task.register_transition(report, task.transitions_to_finish())?;
let listing = "swiftide-core\nswiftide-tasks\nswiftide-agents".to_string(); match task.run(listing).await? { TaskRunOutcome::Completed(line) => println!("{line}"), TaskRunOutcome::Paused => println!("paused"), } Ok(())}Source: snippets/src/start/your_first_task.rs#L5-L30
Task nodes
Section titled “Task nodes”Task::<String, String>::new() creates an empty task. The first type parameter is the input you pass to run. The second is the output it returns.
register_node_fn turns a synchronous closure into a task node. The closure takes a reference to its input and returns a Result. The error type must implement std::error::Error + Send + Sync + 'static, so anyhow::Error does not fit. The closures never fail, so Ok::<_, NodeError>(..) only names a type the compiler can infer. The method returns a typed NodeId, which you use to wire transitions.
discover splits the listing into a Vec<String>. report takes that vector and formats a line. Every input and output type must be Clone + Debug + Send + Sync + 'static. The NodeArg trait covers exactly that bound, so plain owned types work.
Transitions
Section titled “Transitions”starts_with(discover) marks the node that receives the task input.
register_transition connects a node to what runs after it. The closure receives the output of discover and returns report.transitions_with(crates). The compiler checks that the input type of report matches the output type of discover. A mismatch is a compile error, not a runtime surprise.
task.transitions_to_finish() ends the task with the output of report. The finishing node’s output type must equal the task’s output type.
Each node gets exactly one transition. A second register_transition for the same node returns an error. A node without a transition makes run fail with TaskError::MissingTransition, and a task without a start node fails with TaskError::NoSteps.
Run outcome
Section titled “Run outcome”task.run(input) returns Result<TaskRunOutcome<O>, TaskError>. TaskRunOutcome::Completed carries the output. TaskRunOutcome::Paused means a node asked the task to pause, which this graph never does.
cargo run prints:
3 crates: swiftide-core, swiftide-tasks, swiftide-agentsA node error stops the run and comes back as TaskError::NodeError. Its message gives the numeric id of the node that failed, as in Node error in node 0: .... The id is the order in which you registered the node, not the name of the variable that holds it.
To run the same graph again with the last input, call reset and then resume.
Fan-out
Section titled “Fan-out”A transition can also fan out. It schedules one branch per input on the same node, and a join node collects the outputs. The workspace-architect showcase uses this to run one agent per crate. Fan-out and join covers the API.
Common mistakes
Section titled “Common mistakes”- Returning
anyhow::Resultfrom aregister_node_fnclosure does not compile. Use an error type that implementsstd::error::Error, such as your ownthiserrorenum. runon a task that is still active or paused returnsTaskError::TaskActive. Continue a paused task withresume.- A human approval step is
Transition::pause()followed bytask.resume(). See Pause and resume. - For nodes that call a model, see Nodes and transitions and Agents as nodes. An agent node also needs the
swiftide-agentsfeature flag and a provider feature flag such asopenai.