Skip to content

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.

The task needs no model, so the swiftide-tasks feature flag is enough. swiftide-agents also turns swiftide-tasks on.

Terminal window
cargo add swiftide@0.34 --features swiftide-tasks
cargo add tokio --features macros,rt-multi-thread
cargo add anyhow

main builds the task and runs it:

src/main.rs
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::<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.

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.

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-agents

A 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.

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.

  • Returning anyhow::Result from a register_node_fn closure does not compile. Use an error type that implements std::error::Error, such as your own thiserror enum.
  • run on a task that is still active or paused returns TaskError::TaskActive. Continue a paused task with resume.
  • A human approval step is Transition::pause() followed by task.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-agents feature flag and a provider feature flag such as openai.

Using Swiftide with coding agents