Agent Squad Swift logo

Agent Squad Swift

OrganizationPopular
2FastLabs
agent-squad-swift

Use when building or modifying a Swift app that uses the AgentSquad Swift framework — on-device multi-agent orchestration for iOS 16+ / macOS 14+: orchestrator, agents (Agent, GroundedAgent), classifier routing, LLM clients (OpenAI-compatible), tools (native + MCP), tool UIs/widgets, on-device storage, tracing, and realtime voice — built-in types and custom implementations.

Overview

Publisher2FastLabs
Repositoryagent-squad
Skill nameagent-squad-swift
Stars
7.8K
Forks
741
Bundled files
103
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 103 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by 2FastLabs on GitHub. Read the source before you install it.

Installation

Install the Agent Squad Swift AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/2FastLabs/agent-squad.git /tmp/agent-squad
mkdir -p .claude/skills
cp -r /tmp/agent-squad/swift .claude/skills/agent-squad-swift
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Agent Squad Swift in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Agent Squad Swift on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Agent Squad Swift is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

AgentSquad Swift — assistant guide

Protocol-driven, on-device multi-agent framework (Swift 6.2, iOS 16+ / macOS 14+; persistence via FileChatStorage on iOS 16+, DeviceChatStorage on iOS 17+). This is guidance and a map — not an API reference. Read the exact signatures from the source (swift/Sources/AgentSquad/) and the worked recipes from the docs site sources (docs/src/content/docs/swift/); this file tells you what to use, when, and what to watch out for.

When to use what

  • One assistant → an Agent (or GroundedAgent) + an Orchestrator with no classifier. No routing hop.
  • Several specialists → multiple agents + an LLMClassifier; the orchestrator routes each turn.
  • Answers must not drift from data (prices, stock, balances) → GroundedAgent: a Brain calls tools, an isolated Presenter speaks only from the curated results (it can be a smaller/local model).
  • Voice → a VoiceAssistant (a peer of the orchestrator, not an agent): OpenAIVoiceAssistant (single LLM + tools, speaks directly — the spoken analog of Agent) or OpenAIGroundedVoiceAssistant (Brain → Presenter, can't drift from data — analog of GroundedAgent).

Every component is a Sendable protocol with one built-in implementation — swap in your own anywhere.

Modules (import only what you use)

ImportPulls inContents
AgentSquadnothing externalprotocols, Agent, GroundedAgent, Orchestrator, LLMClassifier, ChatCompletionsClient, DakeraRetriever, FileChatStorage, DeviceChatStorage, InMemoryChatStorage, TransformingChatStorage, SummarizingChatStorage, OSLogTracer, OTLP export
AgentSquadMCPMCP Swift SDKMCPServer (= MCPToolProvider), SDKMCPClient
AgentSquadAudioAVFoundationVoiceProcessedAudioIO (capture+playback, one engine, AEC — the recommended wiring), MicCapture (voice-processed/AEC by default), AudioPlayback, VoiceProcessing, AudioSessionPolicy (needs NSMicrophoneUsageDescription)

SwiftPM: .package(url: "https://github.com/2FastLabs/agent-squad", branch: "main").

How a turn works

Two peer runtimes share contracts but not a control loop: a turn-based Orchestrator (classify? → run agent → stream → persist) and a long-lived VoiceAssistant for voice. Either way you consume an AsyncThrowingStream<AgentEvent, any Error> — the one idiom worth memorizing:

swift
for try await event in orchestrator.route(.text("hello"), userId: "u1", sessionId: "s1") {
    switch event {
    case .textDelta(let token): /* stream tokens */
    case .final(let message):   /* the message that was persisted */
    case .toolCall, .widget, .thinking, .error: break   // .error is a user-facing string
    }
}

.error carries a user-facing message; real programmer/transport failures throw through the stream. .final is what the orchestrator persists. Inputs/messages are value types (AgentInput.text, ConversationMessage, ContentPart, JSONValue) in Sources/AgentSquad/Core/.

The pieces

  • Orchestrator drives a turn. The classifier is optional — omit it for a single agent.
  • Agent is one LLM with an internal tool loop. GroundedAgent is two LLMs (Brain + isolated Presenter) for answers that must stay grounded in tool results. The Presenter never sees chat history or the Brain's transcript; presenterInput picks .questionAndData (default) or .dataOnly.
  • ChatCompletionsClient speaks the OpenAI wire — point its baseURL at OpenAI, Azure, OpenRouter, Groq, or a local Ollama/llama.cpp. Implement LLMClient for anything else.
  • Tools come from a ToolProvider. Built-ins: ToolKit holds native tools — Tool.local (Swift closure) and Tool.http/Tool.get/.post (declarative HTTP, with a ToolParameter DSL so you don't hand-write JSON Schema); HTTPToolGroup(baseURL:…) declares one API's shared config once, then one line per endpoint; MCPServer(url:) connects an MCP server; and AggregateToolProvider composes any mix behind one seam; DakeraRetriever(namespace:…) is a ToolProvider backed by a self-hosted Dakera memory server — it exposes a search_memory tool for grounding (and a direct retrieve(_:) API), talking to Dakera's REST endpoint over URLSession with no extra dependency. A ToolResult is three-part: text → the model's context, structuredContent → curator/UI data, ui → an optional widget.
  • FileChatStorage (JSON files, iOS 16+) and DeviceChatStorage (SwiftData, iOS 17+) persist history on-device; InMemoryChatStorage is a non-persistent, seedable single-conversation store. TransformingChatStorage wraps any store and runs a MessageTransform before each save (PII scrub / redact / drop) — reads pass through, message.mappingText { … } covers the text-only case. SummarizingChatStorage wraps any store and keeps agent context small: on the first fetch that exceeds triggerAt message pairs the user-supplied ChatSummarizer is called and the compressed result is held in an in-memory buffer; subsequent saves append to the buffer and recompress eagerly if needed; fetchAllChats is never intercepted so raw history stays available for analytics — the inner store is never written by the summarizer. OSLogTracer is the default tracer; wire ProcessingTracer + OTLPExporter to ship traces to Langfuse/LangSmith/Datadog/…
  • Voice: two VoiceAssistants over a WebSocket — OpenAIVoiceAssistant (single LLM, speaks directly) and OpenAIGroundedVoiceAssistant (grounded Brain → Presenter). Both are self-sufficient (own tracer/store/userId/sessionId; with a store, completed turns persist and prior history seeds on start()), wired to the mic/speaker by RealtimeRuntime. Preferred audio wiring: ONE VoiceProcessedAudioIO instance passed as both input: and output: — capture and playback share one voice-processed AVAudioEngine, so the assistant's audio is guaranteed to be in the echo canceller's reference path. The split MicCapture/AudioPlayback pair also works (capture is voice-processed by default; the AEC reference is then device-level/route-dependent; MicCapture(voiceProcessing: nil) = raw capture). All three audio classes take an AudioSessionPolicy (.managed / .custom / .external for apps that own the AVAudioSession) and a configureEngine hook exposing the raw AVAudioEngine. Session tuning on both: transcriptionModel (the user's STT only), turnDetection (.semanticVAD(eagerness:) / .serverVAD(threshold:…) / .disabled), and sessionOverrides (deep-merged into the generated session.update last — the escape hatch for unmodeled keys like audio.input.noise_reduction). On OpenAIVoiceAssistant additionally reasoning: RealtimeReasoningEffort (.minimal.xhigh, session-wide, reasoning models like gpt-realtime-2 only) and toolReasoningEffort: [String: RealtimeReasoningEffort] — turn-sticky escalation: once a turn calls a listed tool, that turn's subsequent responses are created with the mapped effort (highest wins), so payload synthesis thinks harder while lookups stay fast.

Custom implementations

Conform to the protocol and pass your type where the built-in goes. Each seam has a worked example on its doc page (paths below are under docs/src/content/docs/swift/, published at /agent-squad/swift/…); signatures live in Sources/AgentSquad/.

SeamProtocolSource · doc
AgentAgentProtocolCore/AgentProtocol.swift · agents/custom
ClassifierClassifier (return an agent from the passed list, or nil)Core/Classifier/ · classifiers/custom
LLM clientLLMClientCore/LLMClient.swift · llm/custom
ToolsToolProviderCore/Tooling/ · tools/custom
Tool-output curatorToolOutputCurator (where you trim oversized output)Core/Presenter/ · ui/built-in/curators
Presenter promptPresenterPromptCore/Presenter/ · agents/built-in/grounded-agent
StorageChatStorageCore/Storage/ · storage/custom
TracingTraceExporter (easiest) / SpanProcessor / Tracer / RedactorCore/Tracing/ · tracing/custom
Realtime transportRealtimeTransportRuntimes/Realtime/ · voice/custom
Audio I/OAudioInput / AudioOutputRuntimes/Realtime/AudioIO.swift · audio/custom

Gotchas

  • maxToolRounds: Agent/GroundedAgent default to 20; the AgentProtocol default is 1. A custom agent that injects tools but leaves 1 silently disables its tool loop.
  • Classifier is optional: no classifier ⇒ no routing hop / no extra model call. A nil selection falls back to the default agent (no confidence threshold).
  • Persistence: only turns ending in .final are saved.
  • ChatCompletionsClient: retries only before the first event; some local runtimes reject stream_options/unknown body keys — override via extraBody.
  • JSONValue: whole-number doubles decode to .int; carry large IDs as .string.
  • Storage: FileChatStorage (JSON, iOS 16+, scopes per-call by userId/sessionId/agentId — e.g. sessionId to isolate per match) or DeviceChatStorage (SwiftData, iOS 17+, bound to one userId). Both default to Library/Caches (disposable). InMemoryChatStorage (iOS 16+) is non-persistent and holds one conversation — construct it empty or seeded with a prior conversation to load one into a session. Wrap any store in TransformingChatStorage to scrub/redact before persistence; prefer redacting over returning nil (dropping one side of an exchange can make the store skip its counterpart via the consecutive-same-role guard). Wrap any store in SummarizingChatStorage(wrapping:summarizer:triggerAt:keepLast:) to keep agent context small: the buffer activates lazily on the first qualifying fetch; once active, saves append to it and compress eagerly; fetchAllChats bypasses the buffer entirely.
  • Tracing lifecycle: nothing drains the tracer for you — flush on background, shut down on termination. OSLogTracer logs no payloads. Redaction hashes ids + clips strings but does not pattern-scrub PII — supply a custom Redactor for that. A realtime answer generation (response/presenter) is backdated to its response.created receive-time via SpanHandle.generation(…, startedAt:), so the exported span carries the real call latency instead of a ~0 duration (the Realtime API sends no server-side timing). The overload defaults to stamping now, so custom SpanHandles need not implement it.
  • Realtime is a peer runtime, not an agent; its events stream is non-throwing; needs NSMicrophoneUsageDescription; always stop(). In-band failures arrive as .error(code:message:)code is the API's machine code (e.g. rate_limit_exceeded), response_failed when the turn's live response ends with status: "failed" (a late failed done for a response already cancelled by barge-in is consumed silently), or transport_closed when the socket dies (then events finishes — the end-of-session signal); message is the human-readable detail for logs, not for verbatim display. Failures are recorded on the trace spans they end (turn/session/tool), so they export with status: error instead of vanishing.
  • Barge-in truncation: on interrupt the session sends conversation.item.truncate so the server drops the unheard audio + transcript from context (OpenAI docs' WebSocket procedure). Automatic when wired by RealtimeRuntime with the built-in outputs; a custom AudioOutput gets it by implementing playedMilliseconds() (protocol default returns nil → truncation is skipped, everything else works). Applies to in-band spoken replies only — the grounded presenter is out-of-band (conversation: "none"), its items never enter the conversation, so interrupting it deliberately sends no truncate.
  • Voice processing (AEC): on by default; if it can't be enabled start() throws .voiceProcessingUnavailable (degrade deliberately with MicCapture(voiceProcessing: nil)). For guaranteed echo cancellation use VoiceProcessedAudioIO and pass the same instance as input and output (its start()/stop() are idempotent — the runtime calls each twice). The simulator does no AEC — validate on a device. VP quiets the speaker (counter with duckingLevel: .min); never enable VP on a playback-only engine. With sessionPolicy: .external the app must configure and activate its AVAudioSession before start(), and should use the same policy everywhere.
  • ContentPart Codable keys off case + label names — renaming breaks stored history.

Go deeper

  • Prose & recipes — the Starlight docs under docs/src/content/docs/swift/ (run the site from docs/ with npm run dev): quick-start, orchestrator/overview, agents/built-in/grounded-agent, mcp/overview, ui/overview, storage/built-in/device, tracing/built-in/otlp-exporter, voice/built-in/openai-voice, voice/built-in/openai-grounded-voice, guides/*.
  • Exact signaturesswift/Sources/AgentSquad/ (Core/, Agents/, Core/LLM/, Core/Tooling/, Core/Tracing/, Runtimes/Realtime/).

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

and 43 more files.

Frequently asked questions

What does the Agent Squad Swift AI skill do?

Use when building or modifying a Swift app that uses the AgentSquad Swift framework — on-device multi-agent orchestration for iOS 16+ / macOS 14+: orchestrator, agents (Agent, GroundedAgent), classifier routing, LLM clients (OpenAI-compatible), tools (native + MCP), tool UIs/widgets, on-device storage, tracing, and realtime voice — built-in types and custom implementations.

Why use Agent Squad Swift on TypingMind?

Because you install it once and use it with any model. Agent Squad Swift is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Agent Squad Swift in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/2FastLabs/agent-squad/tree/main/swift. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Agent Squad Swift?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Agent Squad Swift?

As many as you like. As long as a model supports skills, you can use Agent Squad Swift with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Agent Squad Swift AI skill free?

Yes. It is published on GitHub by 2FastLabs under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇