Bosun
All posts

Swiftide 0.33 and 0.34 - Agent contrib tools, typed task graphs, rustdoc tools, and more

Swiftide 0.33 and 0.34 ship file and planning tools for agents, typed task graphs with fan-out and joins, tool schemas read from rustdoc, streaming command output, and new docs at swiftide.rs.

, by Timon Vonk

Swiftide 0.33 and 0.34 are out. I let a year pass without writing about a release. Part of that was forgetting. Part was spending my time cleaning up AI-generated code, and most of it was building Bosun.

Swiftide kept moving in the meantime. Here is what you will notice.

To get started with Swiftide, head over to swiftide.rs, check us out on github, or hit us up on discord.

Contrib tools and hooks for agents

The part I like most. An agent that reads and edits files, keeps a plan, and keeps its context small on long runs used to mean writing those tools yourself. The new swiftide-agents-contrib crate ships them, each behind its own feature flag: agents-contrib-file-tools for read_file, edit and apply_patch, agents-contrib-planning for update_plan, and agents-contrib-context for hooks that summarize large tool output and compress the conversation.

let mut agent = Agent::builder()
.llm(&openai)
.context(DefaultContext::from_executor(executor))
.system_prompt("You fix failing tests. Keep a plan with update_plan.")
.add_tool(ReadFile::default().with_line_numbers())
.add_tool(ExactReplace)
.add_tool(ApplyPatch)
.add_tool(UpdatePlan::default())
.limit(50)
.build()?;
agent
.query("The test `parses_empty_input` in src/parser.rs fails. Fix it.")
.await?;

Every file operation runs through the executor of the context, so the same agent works on a local checkout or inside a container. The contrib page covers the hooks.

Typed task graphs

Tasks moved into their own crate, swiftide-tasks, and the graph is typed from node to node. Fan out to several branches, join them when all have arrived, pause for a human and resume later. The compiler checks what flows where.

task.starts_with(start);
task.register_transition(start, move |text: String| {
Transition::fan_out(&count, text.clone())
.and(&length, text.clone())
.and(&shout, text)
.join_with(join.join())
})?;
let TaskRunOutcome::Completed(summary) = task.run(" hello tasks ").await? else {
anyhow::bail!("nothing in this task pauses");
};

The workspace architect showcase runs one agent per crate in parallel this way and joins the summaries into one document.

Tool descriptions from rustdoc

The #[swiftide::tool] macro now reads the description the model sees from the doc comment, and each item under # Arguments describes one argument. The comment you write for yourself is the schema.

/// Lists every commit since a release tag, oldest first: hash, subject, body and the files it
/// changed.
///
/// # Arguments
/// - `tag`: Tag to start from, e.g. `v0.33.0`. Use null for the latest tag.
#[swiftide::tool]
pub async fn git_log_since(
context: &dyn AgentContext,
tag: &Option<String>,
) -> Result<ToolOutput, ToolError> {

Streaming command output, and the rest

Executors stream stdout and stderr while a command runs, so a cargo test that takes ten minutes shows progress instead of a blank wait:

let cmd = Command::shell("cargo test").with_timeout(Duration::from_secs(600));
let result = executor
.exec_cmd_streaming(&cmd, &mut |chunk: &CommandOutputChunk| {
let _ = chunks.send(chunk.clone());
})
.await;

Also in these releases: images in chat completions, reasoning kept across turns in the OpenAI Responses API, Mistral as a provider, the Bedrock converse API, and MCP through rmcp 2.

New contributors

Seven people landed their first Swiftide change in these two releases. Thank you.

New docs

swiftide.rs is rebuilt. Every Rust block on it compiles against the release, and the showcases are real programs you can clone and run. Each page has a markdown twin and there is an llms.txt, so your coding agent can read it too. The repository ships a Claude Code skill in skills/swiftide with the same material.

Breaking changes

The upgrading page walks through all of them. The ones most people hit:

  • CommandOutput keeps stdout and stderr apart; read them with stdout(), stderr() or to_string_lossy().
  • The agent system prompt is a Prompt; SystemPrompt and its builder are gone.
  • Task::run returns a TaskRunOutcome, and transitions return Transition values.
  • Pipeline::run returns its statistics.

You can find the full changelog here.

If you build LLM things in Rust, give it a try and tell me what breaks.