Show HN: I Built LangGraph for Swift
Summary
Swarm is a Swift framework for building agentic workflows and multi-agent systems, with type-safe tool calling, durable checkpointing, and support for multiple LLM providers.
View Cached Full Text
Cached at: 07/06/26, 05:05 PM
christopherkarani/Swarm
Source: https://github.com/christopherkarani/Swarm
let result = try await Workflow()
.step(researchAgent)
.step(writerAgent)
.run("Summarize the latest WWDC session on Swift concurrency.")
Two agents, one pipeline, compiled to a DAG with crash recovery and Swift concurrency safety.
Install
.package(url: "https://github.com/christopherkarani/Swarm.git", from: "0.6.0")
Quick Start
import Swarm
// The @Tool macro generates the JSON schema at compile time
@Tool("Looks up the current stock price")
struct PriceTool {
@Parameter("Ticker symbol") var ticker: String
func execute() async throws -> String { "182.50" }
}
// Create an agent with unlabeled instructions first and tools in the trailing @ToolBuilder closure
let agent = try Agent("Answer finance questions using real data.",
configuration: .init(name: "Analyst"),
inferenceProvider: .anthropic(key: "{ENV")) {
PriceTool()
CalculatorTool()
}
let result = try await agent.run("What is AAPL trading at?")
print(result.output) // "Apple (AAPL) is currently trading at $182.50."
That is a working agent with type-safe tool calling. Swarm also supports AGENTS.md and SKILL.md for declarative agent specs and reusable skills — see the Getting Started guide for the full workspace layout.
Why Swarm
- Swift concurrency is part of the surface. Swift 6.2
StrictConcurrencyis enabled across the package. - Tools stay type-safe. The
@Toolmacro generates JSON schemas from Swift structs. - Workflows can survive crashes. Durable workflow checkpointing lets you resume from an explicit checkpoint ID.
- Cloud and on-device models use the same abstractions. Foundation Models, Anthropic, OpenAI, Ollama, Gemini, MiniMax, OpenRouter, and MLX all fit the same shape.
- It is written in Swift all the way down.
AsyncThrowingStream, actors, result builders, and macros are first-class here.
Examples
Capability matrix showcase
Swarm now ships with an in-repo capability showcase that exercises the stable surface area in one deterministic matrix:
- agents and tools
- streaming
- conversation plus session persistence
- sequential, parallel, routed, and repeat-until workflows
- handoffs
- memory
- on-device workspace loading
- guardrails
- resilience helpers
- durable checkpoint and resume
- observability
- MCP discovery and tool bridging
- provider selection
Run it locally:
swift run SwarmCapabilityShowcase list
swift run SwarmCapabilityShowcase matrix
swift run SwarmCapabilityShowcase run handoff
swift run SwarmCapabilityShowcase smoke
The deterministic matrix is CI-safe. Live-provider smoke coverage is opt-in through environment variables. See docs/guide/capability-showcase.md for the scenario catalog and smoke-mode details.
Optional demos
Demo executables are opt-in so the default library graph stays focused on the framework products:
SWARM_INCLUDE_DEMO=1 swift build
SWARM_INCLUDE_DEMO=1 swift run SwarmDemo
SWARM_INCLUDE_DEMO=1 swift run SwarmMCPServerDemo
Multi-agent pipeline
let researcher = try Agent("Research the topic and extract key facts.",
inferenceProvider: .anthropic(key: "sk-...")) {
WebSearchTool()
}
let writer = try Agent("Write a concise summary from the research.",
inferenceProvider: .anthropic(key: "sk-..."))
let result = try await Workflow()
.step(researcher)
.step(writer)
.run("Latest advances in on-device ML")
Each agent resolves its own provider. Pass inferenceProvider: per agent (as above), or call await Swarm.configure(provider: .anthropic(apiKey: "...")) once at app startup to share a default across every agent that doesn’t specify one.
Parallel fan-out
let result = try await Workflow()
.parallel([bullAgent, bearAgent, analystAgent], merge: .structured)
.run("Evaluate Apple's Q4 earnings.")
// Three perspectives, merged into one output.
Dynamic routing
let result = try await Workflow()
.route { input in
if input.contains("$") { return mathAgent }
if input.contains("weather") { return weatherAgent }
return generalAgent
}
.run("What is 15% of $240?")
Streaming
for try await event in agent.stream("Summarize the changelog.") {
switch event {
case .output(.token(let t)): print(t, terminator: "")
case .tool(.completed(let call, _)): print("\n[tool: \(call.toolName)]")
case .lifecycle(.completed(let r)): print("\nDone in \(r.duration)")
case .lifecycle(.failed(let error)): print("\nError: \(error)")
default: break // Other events include .output(.thinking(...)), .handoff(...), .observation(...), and .lifecycle(.iterationStarted(...)).
}
}
More examples
Semantic memory
let agent = try Agent("You remember past conversations.",
inferenceProvider: .anthropic(key: "sk-..."),
memory: .vector(embeddingProvider: myEmbedder, similarityThreshold: 0.75)) {
// tools
}
Guardrails
let agent = try Agent("You are a helpful assistant.",
inputGuardrails: [InputGuard.maxLength(5000), InputGuard.notEmpty()],
outputGuardrails: [OutputGuard.maxLength(2000)])
Closure tools
let reverse = FunctionTool(
name: "reverse",
description: "Reverses a string",
parameters: [ToolParameter(name: "text", description: "Text to reverse", type: .string, isRequired: true)]
) { args in
let text = try args.require("text", as: String.self)
return .string(String(text.reversed()))
}
let agent = try Agent("Text utilities.", tools: [reverse])
Crash-resumable workflows
let workflow = Workflow()
.step(monitor)
.durable.checkpoint(id: "monitor-v1", policy: .everyStep)
.durable.checkpointing(.fileSystem(directory: checkpointsURL))
let resumed = try await workflow.durable.execute("watch", resumeFrom: "monitor-v1")
Provider switching
// On-device, private, no API key needed
let local = try Agent("Be helpful.", inferenceProvider: .foundationModels())
// Cloud
let cloud = try Agent("Be helpful.", inferenceProvider: .anthropic(key: k))
// Or swap at runtime via environment
let modified = agent.environment(\.inferenceProvider, .ollama(model: "mistral"))
Conversation
let conversation = Conversation(with: agent)
let response1 = try await conversation.send("What's the weather?")
let response2 = try await conversation.send("And tomorrow?") // Context preserved
for message in await conversation.messages {
print("\(message.role): \(message.text)")
}
How Swarm Compares
| Swarm | LangChain | AutoGen | |
|---|---|---|---|
| Language | Swift 6.2 | Python | Python |
| Data race safety | Compile-time | Runtime | Runtime |
| On-device LLM | Foundation Models | n/a | n/a |
| Execution model | Typed Workflow graph | Loop-based | Loop-based |
| Crash recovery | Checkpoints | n/a | Partial |
| Type-safe tools | @Tool macro (compile-time) | Decorators (runtime) | Runtime |
| Streaming | AsyncThrowingStream | Callbacks | Callbacks |
| iOS / macOS native | First-class | n/a | n/a |
What’s Included
| Agents | Agent struct with @ToolBuilder trailing closure, AgentRuntime protocol |
| Workflows | Workflow: .step(), .parallel(), .route(), .repeatUntil(), .timeout() |
| Tools | @Tool macro, FunctionTool, @ToolBuilder, parallel execution |
| Memory | .conversation(maxMessages:), .vector(embeddingProvider:similarityThreshold:maxResults:), .slidingWindow(maxTokens:), .summary(configuration:summarizer:), .hybrid(configuration:summarizer:) |
| Guardrails | InputGuard.maxLength(), InputGuard.notEmpty(), InputGuard.custom(), OutputGuard.maxLength(), OutputGuard.custom() |
| Conversation | Conversation actor for stateful multi-turn dialogue |
| Resilience | 7 backoff strategies, circuit breaker, fallback chains, rate limiting |
| Observability | AgentObserver, Tracer, SwiftLogTracer, per-agent token metrics |
| MCP | Model Context Protocol client and server support |
| Providers | Foundation Models, Anthropic, OpenAI, Ollama, Gemini, MiniMax, OpenRouter, MLX via Conduit |
| Macros | @Tool, @Parameter, @Traceable, #Prompt |
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
│ iOS 26+ · macOS 26+ · Linux (Ubuntu 22.04+) │
├─────────────────────────────────────────────────────────────┤
│ Workflow · Conversation · .run() · .stream() │
├─────────────────────────────────────────────────────────────┤
│ Agents Memory Tools │
│ Agent (struct) Memory factories @Tool macro │
│ AgentRuntime Conversation FunctionTool │
│ (dot-syntax) @ToolBuilder │
├─────────────────────────────────────────────────────────────┤
│ InputGuard · OutputGuard · Resilience · Observability · MCP│
├─────────────────────────────────────────────────────────────┤
│ Durable Graph Runtime (internal) │
│ Workflow Graph · Checkpointing · Deterministic retry │
├─────────────────────────────────────────────────────────────┤
│ InferenceProvider (pluggable) │
│ Foundation Models · Anthropic · OpenAI · Ollama · OpenRouter│
└─────────────────────────────────────────────────────────────┘
Requirements
| Platform | Minimum |
|---|---|
| Swift | 6.2+ |
| iOS | 26.0+ |
| macOS | 26.0+ |
| tvOS | 26.0+ |
| Linux | Ubuntu 22.04+ with Swift 6.2 |
The default Swarm graph is CI-tested on Ubuntu with Swift 6.2. Apple-only features such as Foundation Models, SwiftData, OSLog, and some built-in tool behavior are unavailable or different on Linux; cloud providers and Ollama use the shared InferenceProvider surface.
Documentation
| Getting Started | Installation, first agent, workflows |
| OpenTelemetry Tracing | Export agent and LLM spans, with optional trace header injection for provider HTTP requests |
| API Reference | Every type, protocol, and API |
| Front-Facing API | Public API surface |
| Why Swarm? | Design philosophy and architecture |
Contributing
- Fork → branch →
swift test→ PR - All public types must be
Sendable; the compiler enforces it - Format with
swiftformat Sources Tests --lint --config .swiftformat
Bug reports and feature requests: GitHub Issues
Community
GitHub Issues · Discussions · @ckarani7
If Swarm saves you time, a star helps others find it.
License
Released under the MIT License.
Similar Articles
I reimplemented Arbor (a research agent that grows a tree of hypotheses) on LangGraph — an agent that keeps its experiments instead of forgetting what failed
A developer reimplements Arbor, a multi-agent research system from a paper, on LangGraph, sharing lessons about graph structure enforcing invariants and backpropagating insights from failures. The reimplementation, arbor-lg, uses LangGraph's StateGraph and SQLite checkpointer for durability.
I'm increasingly convinced LangGraph beats Claude Plugins
The author shares their experience building a Claude Plugin for stock valuation, finding it increasingly difficult to handle errors and complexity. They are now considering switching to LangGraph as a more reliable solution for multi-step agent workflows.
Built my own agent runtime after hitting the ceiling with LangGraph — UI as graph nodes, Postgres durability, zero orchestration cost
The author introduces cascaide, a fullstack agent runtime and AI orchestration framework in TypeScript that runs anywhere JS/TS can. It offers UI as graph nodes, durable Postgres checkpointing, zero orchestration cost, and is designed to be self-hosted without vendor lock-in.
@h100envy: This paper completely changed how I think about a swarm of agents: Describe an agent as a graph -> Nodes are operations…
A paper presents a framework where LLM agents are represented as computational graphs with nodes as operations and edges as information flow, enabling automatic optimization of both node prompts and edge connectivity via RL, turning scattered agent swarms into a single optimizable graph.
You asked for DeepLearning.ai-style notebooks for AgentSwarms—so we built 67 of them (TypeScript/LangChain/LangGraph/LlamaIndex/AgentsSDK/VercelAI).
AgentSwarms launched 67 free, TypeScript-based interactive notebooks for learning multi-agent systems, covering LangChain fundamentals to production-grade error handling and failure modes.