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. Crash recovery is opt-in β enable the Integrations trait and durable checkpointing β and Swift concurrency safety is enforced at compile time.
Install
Default link is lean: core Swarm + on-device Foundation Models. Graph/memory/web/Hive paths are trait-gated (off by default) and are not linked into Swarm unless you enable Integrations.
HiveCore, Membrane, and ContextCore are native in-tree Sources/ targets (internal modules β not separate library products). Enabling Integrations links those modules plus Wax (still remote) and SwiftSoup; omitting the trait does not link them into Swarm. Lean resolve never pulls Hive/Membrane/ContextCore/Conduit package identities, and (with trait-gated product edges) also does not pin Wax, MetalANNSβGRDB, swift-crypto, swift-mutex, or SwiftSoup. Default remotes remain (swift-syntax via the default-on Macros trait, swift-log, MCP sdk, OTel, plus NIO transitives β including swift-collections via NIO). Disable Macros with traits: [] to drop swift-syntax; use FunctionTool instead of @Tool. SWARM_CORE_ONLY=1 drops the integration package block entirely. ContextCore / full Membrane session stack require Apple platforms (Metal/CoreML); Linux Integrations still builds Hive + MembraneCore + web helpers. DefaultAgentMemory uses a CoreML MiniLM model that is not bundled β call SemanticEmbeddingAvailability.ensureModelAvailable() to download it on demand. Without the model, ContextCore falls back to deterministic pseudo-embeddings, logs a once-per-process warning naming that API, and DefaultAgentMemory.isSemanticMemoryAvailable reports false.
Root-package note: bare swift build / swift test on this repo compile every registered target, so integration modules need either --traits Integrations or the lean CI helper (scripts/ci/lean-build-test.sh). App consumers only build reachable targets and stay lean without that helper.
// Lean default link (recommended for most apps). Macros are on by default. .package(url: "https://github.com/christopherkarani/Swarm.git", from: "0.6.2") // Full integrations: durable Hive workflows, ContextCore+Wax default memory, // Membrane adapters, and web helpers. Integrations also enables Macros. .package( url: "https://github.com/christopherkarani/Swarm.git", from: "0.6.2", traits: ["Integrations"] ) // Macro-free lean: drops swift-syntax. You lose @Tool, @Parameter, #Prompt, // and @Traceable β use FunctionTool instead. .package( url: "https://github.com/christopherkarani/Swarm.git", from: "0.6.2", traits: [] )
// FunctionTool compiles without the Macros trait let echo = FunctionTool( name: "echo", description: "Echoes a message", parameters: [ ToolParameter(name: "message", description: "Text to echo", type: .string) ] ) { args in let message = try args.require("message", as: String.self) return .string(message) }
From a checkout of this package:
# Lean (root package): product-scoped build, or scripts/ci/lean-build-test.sh swift build --product Swarm --product SwarmMCP --product SwarmOpenTelemetry \ --product SwarmMembrane --product SwarmCapabilityShowcase # SwarmMembrane is a deprecated hollow re-export of Swarm; import Swarm # instead. The product will be removed in 0.7.0. # Full graph swift build --traits Integrations swift test --no-parallel --traits Integrations swift run --traits Integrations SwarmCapabilityShowcase matrix
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 // Built-in backend: Apple Foundation Models (no API key on supported devices) let agent = try Agent("Answer finance questions using real data.", configuration: .default.name("Analyst"), inferenceProvider: .foundationModels()) { 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 checkpointing (Integrations trait) lets you resume from an explicit checkpoint ID.
- Built-in inference is on-device Foundation Models, plus an OpenAI-compatible remote provider. Linux and machines without Apple Intelligence use
.openAICompatible(...); the agent loop stays the same. - 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:
# Capability showcase matrix covers durable workflows; enable Integrations
swift run --traits Integrations SwarmCapabilityShowcase list
swift run --traits Integrations SwarmCapabilityShowcase matrix
swift run --traits Integrations SwarmCapabilityShowcase run handoff
swift run --traits Integrations SwarmCapabilityShowcase smokeThe 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.
End-to-end example apps
Two minimal, buildable apps under Examples/ stress the public API:
| Example | What it proves |
|---|---|
Examples/OnDeviceChat |
Foundation Models chat with @Tool, streaming, and multi-turn Conversation (zero API keys; --demo for CI) |
Examples/MultiAgentPipeline |
Sequential + parallel workflows and durable checkpoint/resume (--demo for CI; requires Integrations) |
Examples/WaxChat |
Wax durable memory + websearch chat (--demo for CI; requires Integrations) |
Examples/CodeReviewer |
Lightweight CLI that links Swarm and prints a deterministic review plan |
cd Examples/OnDeviceChat && swift run OnDeviceChat --demo cd Examples/MultiAgentPipeline && swift run MultiAgentPipeline --demo cd Examples/WaxChat && swift run WaxChat --demo
Optional demos
Package-root 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
Foundation Models First
For Apple platforms, use the built-in on-device path β no API keys:
import Swarm // Requires macOS/iOS 26+ and Apple Intelligence available on the device. let agent = try Agent( "You are a private on-device assistant.", inferenceProvider: .foundationModels() ) { // @Tool structs or FunctionTool values } let result = try await agent.run("Summarize my notes.")
Notes that matter in production:
- Availability: use
FoundationModelsInferenceProvider.ifAvailable()or checkFoundationModelsInferenceProvider.isAvailablebefore assuming the system model is ready. - Tool calling: Swarm bridges
@Tool/ToolSchemato Apple'sFoundationModels.Tooland executes tools in the agent loop with guardrails intact (capture mode, default β all tool calls from a parallel group are recovered). Opt in to experimental native session mode for Apple's inner tool loop and token streaming with tools. - Streaming tool calls: not advertised as token-level tool streaming;
Agent.streamobserves the samerunloop viaAgentEvent(lifecycle, tools, and.output(.token)chunks). Foundation Models yields incremental text deltas; providers without a streaming API emit the full response as a single chunk. - Structured outputs:
runStructureduses Foundation Models guided generation when the JSON Schema maps ontoGenerationSchema(source: .providerNative); otherwise it is prompt instruction + parse (source: .promptFallback)..jsonObjectalways uses the fallback path. - Dynamic profiles:
.foundationModels(profile:)re-resolves instructions/tools/history every turn (WWDC 2026βaligned Swarm API). - Linux / CI: Foundation Models is compile-time gated. Use
.openAICompatible(.ollama(model:))(or any OpenAI-compatible host), inject a mock, or use the deterministic--demomodes inExamples/.
OpenAI-compatible remote provider
No Apple Intelligence required. Same agent loop, URLSession only:
// Local (Ollama). Data stays on loopback HTTP β not on-device Foundation Models. let local = try Agent( "Be helpful.", inferenceProvider: .openAICompatible(.ollama(model: "llama3.2")) ) // Cloud. Prompt content leaves the device. let cloud = try Agent( "Be helpful.", inferenceProvider: .openAICompatible( .openAI(apiKey: "sk-...", model: "gpt-4o") ) )
| Host | Factory | Leaves the device? |
|---|---|---|
| OpenAI | .openAI(apiKey:model:) |
Yes β to OpenAI |
| Azure OpenAI | .azureOpenAI(resource:deployment:apiKey:) |
Yes β to Azure |
| OpenRouter | .openRouter(apiKey:model:) |
Yes β to OpenRouter |
| Ollama | .ollama(model:) |
Yes β to localhost HTTP |
| LM Studio | .lmStudio(model:) |
Yes β to localhost HTTP |
See Remote Providers for full snippets, structured-output honesty, and live Ollama test setup.
Multi-agent pipeline
// WebSearchTool requires the Integrations trait and an API key // (lean builds compile this initializer, warn immediately, and throw on execute). let researcher = try Agent("Research the topic and extract key facts.", inferenceProvider: .foundationModels()) { WebSearchTool(apiKey: "YOUR_API_KEY") } let writer = try Agent("Write a concise summary from the research.", inferenceProvider: .foundationModels()) 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: myProvider) 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
Agent.stream runs the same agent loop as run, forwarding AgentEvent values through an observer. You get lifecycle, tool, and output events as they happen β not a separate token decoder.
.output(.token) is an incremental text chunk when the provider streams (Foundation Models does). If the provider only has a completion API, that event is the full response in one chunk. Tool calls still complete as capture-then-execute turns unless the provider implements tool-call 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.", memory: .vector(embeddingProvider: myEmbedder, similarityThreshold: 0.75), inferenceProvider: .foundationModels()) { // 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.") { 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")
A mid-step crash re-runs that whole step. File stores keep the newest 16
checkpoints per run (configurable) and identify steps by kind, position, and
optional signature: β not source line numbers. See
Durable Execution.
Provider selection
// Built-in: on-device Foundation Models (no API key) let local = try Agent("Be helpful.", inferenceProvider: .foundationModels()) // Built-in: OpenAI-compatible remote / local HTTP (Linux-friendly) let remote = try Agent( "Be helpful.", inferenceProvider: .openAICompatible(.ollama(model: "llama3.2")) ) // Custom backend: any type conforming to InferenceProvider let custom = try Agent("Be helpful.", inferenceProvider: myCustomProvider) // Or swap at runtime via environment let modified = agent.environment(\.inferenceProvider, myCustomProvider)
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 (Integrations) | 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 when the provider reports usage (Foundation Models does not) |
| MCP | Model Context Protocol client and server support |
| Providers | Built-in Apple Foundation Models (on-device) and OpenAICompatibleProvider (OpenAI / Azure / OpenRouter / Ollama / LM Studio); inject any InferenceProvider for other backends |
| 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 Β· OpenAI-compatible Β· custom provider β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
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; use OpenAICompatibleProvider (stubbed in CI, live against Ollama when SWARM_OLLAMA_LIVE_TESTS=1) or inject a mock.
Documentation
| Getting Started | Installation, first agent, workflows |
| Remote Providers | OpenAI-compatible provider (OpenAI, Azure, OpenRouter, Ollama, LM Studio) |
| OpenTelemetry Tracing | OTLP/HTTP JSON export of agent and LLM spans, plus W3C traceparent on outbound HTTP |
| 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.
