Open Agent SDK (Swift) [![Product Hunt](https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1158873&theme=light)](https://www.producthunt.com/products/openagent-sdk-for-swift?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-openagent-sdk-for-swift) [![Swift](https://img.shields.io/badge/Swift-6.1-orange)](https://swift.org) [![Platform](https://img.shields.io/badge/platform-macOS%2013%2B-blue)](https://developer.apple.com/macos/) [![CI](https://github.com/terryso/open-agent-sdk-swift/actions/workflows/ci.yml/badge.svg)](https://github.com/terryso/open-agent-sdk-swift/actions/workflows/ci.yml) [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/terryso/fd930407e9ccdbba80306ea7993dcd4d/raw/coverage.json)](https://github.com/terryso/open-agent-sdk-swift/actions) [![BMAD](https://bmad-badge.vercel.app/terryso/open-agent-sdk-swift.svg)](https://github.com/bmad-code-org/BMAD-METHOD) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE) [![DeepWiki](https://img.shields.io/badge/DeepWiki-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data:image/svg%2Bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMiAzaDZhMTAgMTAgMCAwIDEgMTAgMTB2MiIvPjxwYXRoIGQ9Ik0yIDEzaDYxMCAxMCAwIDAgMSAxMCAxMHYyIi8+PC9zdmc+)](https://deepwiki.com/terryso/open-agent-sdk-swift)
[中文文档](./README_CN.md) Open-source Agent SDK for Swift — run the full agent loop **in-process** with native Swift concurrency. Build AI-powered applications with streaming responses, 34 built-in tools, sub-agent orchestration, MCP integration, session persistence, and multi-provider LLM support. > **Inspired by** [open-agent-sdk-typescript](https://github.com/codeany-ai/open-agent-sdk-typescript) — bringing the same agentic architecture to the Swift ecosystem. Also available in **TypeScript**: [open-agent-sdk-typescript](https://github.com/codeany-ai/open-agent-sdk-typescript) | **Go**: [open-agent-sdk-go](https://github.com/codeany-ai/open-agent-sdk-go) ## Highlights - **Full Agent Loop** — Prompt, tool execution, and response in a single `await` call or streaming `AsyncStream` - **34 Built-in Tools** — Core file/search/web tools, advanced task/team management, specialist cron/plan/worktree tools - **Multi-Provider LLM** — Anthropic (Claude) and OpenAI-compatible APIs (GLM, Ollama, OpenRouter, etc.) - **MCP Integration** — Connect external tools via stdio, SSE, HTTP, or in-process MCP servers - **Session Persistence** — Save, load, fork, and manage conversation transcripts as JSON - **Hook System** — 20+ lifecycle events with function and shell hook handlers - **Permission Control** — 6 permission modes plus custom authorization callbacks with policy composition - **Sub-Agent Orchestration** — Spawn child agents, manage teams, tasks, and inter-agent messaging - **Auto-Compaction** — Automatically compresses long conversations to stay within context limits - **Skills System** — 5 built-in skills (Commit, Review, Simplify, Debug, Test) with custom skill registration - **File Cache & Context** — LRU file cache, Git status auto-injection, project document discovery (CLAUDE.md/AGENT.md) - **Runtime Controls** — Dynamic model switching, query abort with partial results, session memory - **Sandbox & Logging** — Configurable sandbox for command/path restrictions, structured JSON logging - **HTTP API Server** — Expose any Agent as a REST + SSE service with run tracking, concurrency limits, and auth - **Cost & Trace** — Built-in CostTracker for token/cost budgeting, TraceRecorder for JSONL execution observability - **Enhanced Memory** — Fact-based memory with candidate→active→retired lifecycle, evidence-driven confidence, and import/export - **Self-Evolution** — ExperienceExtractor for memory auto-extraction, SkillEvolver for skill adaptation, ReviewAgent background pipeline, and IntelligentCurator for LLM-driven skill library curation with Markdown/YAML reports - **Output Formatting** — SDKMessageOutputHandler protocol with Terminal and JSON output formatters - **Runtime Event Layer** — 18 typed events via EventBus, SSE bridge for HTTP API, optional token streaming ## Quick Start (15 minutes) ### Installation Add the dependency in your `Package.swift`: ```swift dependencies: [ .package(url: "https://github.com/terryso/open-agent-sdk-swift.git", from: "0.1.0") ], targets: [ .target(name: "YourApp", dependencies: ["OpenAgentSDK"]) ] ``` Or in Xcode: **File > Add Package Dependencies** and enter the repository URL. ### Configuration Set your API key via environment variable: ```bash export CODEANY_API_KEY=sk-... ``` ### Your First Agent ```swift import OpenAgentSDK let agent = createAgent(options: AgentOptions( apiKey: "sk-...", model: "claude-sonnet-4-6", systemPrompt: "You are a helpful assistant.", maxTurns: 10, permissionMode: .bypassPermissions )) let result = await agent.prompt("Explain Swift concurrency in one paragraph.") print(result.text) print("Used \(result.usage.inputTokens) input + \(result.usage.outputTokens) output tokens") ``` ### Streaming Response ```swift // Using the agent created above: for await message in agent.stream("Read Package.swift and summarize it.") { switch message { case .partialMessage(let data): print(data.text, terminator: "") case .toolUse(let data): print("Using tool: \(data.toolName)") case .result(let data): print("\nDone (\(data.numTurns) turns, $\(String(format: "%.4f", data.totalCostUsd)))") default: break } } ``` ### Custom Tools ```swift struct WeatherInput: Codable { let city: String } let weatherTool = defineTool( name: "get_weather", description: "Get current weather for a city", inputSchema: [ "type": "object", "properties": [ "city": ["type": "string", "description": "City name"] ], "required": ["city"] ] ) { (input: WeatherInput, context: ToolContext) in return "Weather in \(input.city): 22C, sunny" } let agent = createAgent(options: AgentOptions( apiKey: "sk-...", tools: [weatherTool] )) ``` ## Built With This SDK - [**SwiftWork**](https://github.com/terryso/SwiftWork) — A native macOS app built with Swift, powered by this SDK for AI-driven workflows. - [**axion**](https://github.com/terryso/axion) — A Swift CLI tool that uses this SDK as its agentic engine. ## Advanced Features ### Multi-Provider Support Use OpenAI-compatible APIs (GLM, Ollama, OpenRouter, etc.): ```swift let agent = createAgent(options: AgentOptions( provider: .openai, apiKey: "sk-...", model: "gpt-4o", baseURL: "https://api.openai.com/v1", systemPrompt: "You are a helpful assistant." )) ``` Or via environment variables: ```bash export CODEANY_API_KEY=sk-... export CODEANY_BASE_URL=https://api.openai.com/v1 export CODEANY_MODEL=gpt-4o ``` ### Session Persistence Save and restore conversation history: ```swift let sessionStore = SessionStore() let agent = createAgent(options: AgentOptions( apiKey: "sk-...", sessionStore: sessionStore, sessionId: "my-session" )) // First conversation is auto-saved after prompt/stream let result = await agent.prompt("Remember: my favorite color is blue.") // Resume in a new process — history is auto-loaded let agent2 = createAgent(options: AgentOptions( apiKey: "sk-...", sessionStore: sessionStore, sessionId: "my-session" )) let result2 = await agent2.prompt("What is my favorite color?") ``` ### Hook System Register lifecycle event handlers: ```swift let hookRegistry = HookRegistry() await hookRegistry.register(.postToolUse, definition: HookDefinition( handler: { input in if let toolName = input.toolName { print("Tool completed: \(toolName)") } return nil } )) await hookRegistry.register(.preToolUse, definition: HookDefinition( matcher: "Bash", handler: { input in return HookOutput(message: "Bash command blocked", block: true) } )) let agent = createAgent(options: AgentOptions( apiKey: "sk-...", hookRegistry: hookRegistry )) ``` ### Permission Control Choose from 6 permission modes or define custom policies: ```swift // Built-in modes let agent = createAgent(options: AgentOptions( apiKey: "sk-...", permissionMode: .acceptEdits )) // Custom authorization callback agent.setCanUseTool { tool, input, context in if tool.name == "Bash" { return .deny("Bash is disabled") } return .allow() } // Policy composition let policy = CompositePolicy(policies: [ ReadOnlyPolicy(), ToolNameDenylistPolicy(deniedToolNames: ["WebFetch"]) ]) agent.setCanUseTool(canUseTool(policy: policy)) ``` ### MCP Integration Connect external tool servers via MCP (Model Context Protocol): ```swift let agent = createAgent(options: AgentOptions( apiKey: "sk-...", mcpServers: [ "filesystem": .stdio(McpStdioConfig( command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] )), "remote": .sse(McpSseConfig( url: "http://localhost:3001/sse" )) ] )) // MCP tools are auto-discovered and merged into the agent's tool pool ``` ### Budget Control Set cost limits to cap LLM spending: ```swift let agent = createAgent(options: AgentOptions( apiKey: "sk-...", maxBudgetUsd: 0.10 // Stop when cost exceeds $0.10 )) ``` ### Skills System Register built-in or custom skills that encapsulate prompt templates and tool restrictions: ```swift import OpenAgentSDK // Built-in skills are auto-registered let registry = SkillRegistry() registry.register(BuiltInSkills.commit) registry.register(BuiltInSkills.review) // Register a custom skill let explainSkill = Skill( name: "explain", description: "Explain code in detail", promptTemplate: "Read the specified files and explain the code line by line...", toolRestrictions: [.bash, .read, .glob, .grep] ) registry.register(explainSkill) let agent = createAgent(options: AgentOptions( apiKey: "sk-...", tools: getAllBaseTools(tier: .core) + [createSkillTool(registry: registry)] )) ``` ### Runtime Model Switching Switch LLM models mid-conversation with per-model cost tracking: ```swift let agent = createAgent(options: AgentOptions( apiKey: "sk-...", model: "claude-sonnet-4-6" )) // Use fast model for simple queries let result1 = await agent.prompt("Quick question...") // Switch to powerful model for complex tasks try agent.switchModel("claude-opus-4-6") let result2 = await agent.prompt("Analyze this complex codebase...") // result2.usage.costBreakdown contains separate entries per model ``` ### Query Abort Cancel running queries and retrieve partial results: ```swift let task = Task { for await message in agent.stream("Long-running analysis...") { // process events } } // Cancel after timeout task.cancel() // The agent returns a QueryResult with isCancelled=true and partial results ``` ### Context Injection Automatic Git status and project document discovery: ```swift let agent = createAgent(options: AgentOptions( apiKey: "sk-...", projectRoot: "/path/to/project" // auto-discovers CLAUDE.md, AGENT.md )) // System prompt now includes and blocks ``` ### Sandbox & Logging Restrict agent operations and capture structured logs: ```swift let agent = createAgent(options: AgentOptions( apiKey: "sk-...", sandbox: SandboxSettings( allowedReadPaths: ["/project/"], allowedWritePaths: ["/project/src/"], deniedCommands: ["rm", "sudo"] ), logLevel: .debug, logOutput: .custom { jsonLine in // Integrate with ELK, Datadog, etc. print(jsonLine) } )) ``` ### HTTP API Server Expose any Agent as a REST + SSE HTTP service: ```swift let agent = createAgent(options: AgentOptions( apiKey: "sk-...", tools: getAllBaseTools(tier: .core) )) let server = AgentHTTPServer( agent: agent, host: "127.0.0.1", port: 4242, authKey: "my-secret-key", maxConcurrentRuns: 5 ) try await server.start() // POST /v1/runs — submit tasks (202 + run_id) // GET /v1/runs — list runs // GET /v1/runs/{id} — get run status // GET /v1/runs/{id}/events — SSE stream // GET /v1/health — health check ``` ### Cost Tracking & Tracing Monitor agent spending and trace execution: ```swift let agent = createAgent(options: AgentOptions( apiKey: "sk-...", maxBudgetUsd: 0.50, traceEnabled: true, traceBaseURL: "/tmp/traces" )) for await message in agent.stream("Analyze the codebase") { // CostTracker accumulates per-model token/cost automatically // TraceRecorder writes JSONL events to /tmp/traces/{runId}/trace.jsonl } ``` ### Output Formatting Format SDK message streams for terminal or JSON output: ```swift // Terminal output with step counting let terminal = TerminalOutputHandler() for await message in agent.stream("Summarize this project") { terminal.handle(message) } // JSON output for programmatic consumption let json = JSONOutputHandler(write: { print($0) }) terminal.displayRunStart(runId: "run-1", task: "Summarize") for await message in agent.stream("Summarize this project") { json.handle(message) } let result = json.finalize() // [String: Any] dictionary ``` ### Runtime Event Layer Subscribe to typed runtime events via `EventBus` — session lifecycle, agent progress, tool execution, LLM cost tracking, and token streaming. ```swift let eventBus = EventBus() // Subscribe to all events let (_, stream) = await eventBus.subscribe() for await event in stream { switch event { case let e as AgentStartedEvent: print("Agent started: \(e.task)") case let e as ToolCompletedEvent: print("Tool done: \(e.toolName) in \(e.durationMs)ms") case let e as LLMCostEvent: print("Cost: $\(String(format: "%.4f", e.estimatedCostUsd))") default: break } } // Or subscribe to a specific event type only let costStream = await eventBus.subscribe(LLMCostEvent.self) ``` Pass the EventBus to an agent and all events are emitted automatically: ```swift let agent = createAgent(options: AgentOptions( apiKey: "sk-...", eventBus: eventBus, // opt-in, nil = zero overhead emitTokenStream: true // enable LLMTokenStreamEvent for TUI rendering )) ``` **18 event types across 4 categories:** | Category | Events | |----------|--------| | **Session** | `SessionCreatedEvent`, `SessionRestoredEvent`, `SessionClosedEvent`, `SessionAutoSavedEvent` | | **Agent** | `AgentStartedEvent`, `AgentCompletedEvent`, `AgentFailedEvent`, `AgentInterruptedEvent`, `AgentResumedEvent` | | **Tool** | `ToolStartedEvent`, `ToolStreamingEvent`, `ToolCompletedEvent`, `ToolFailedEvent` | | **LLM** | `LLMRequestStartedEvent`, `LLMResponseReceivedEvent`, `LLMCostEvent`, `LLMTokenStreamEvent` | Bridge EventBus to SSE for HTTP API: ```swift let broadcaster = EventBroadcaster() let bridge = EventBusBridge(eventBus: eventBus, broadcaster: broadcaster, runId: "run-1") await bridge.start() // SSE clients now receive typed events via broadcaster ``` ## Built-in Tools ### Core Tools (10) | Tool | Description | | ------------- | ---------------------------------------------- | | **Bash** | Execute shell commands with timeout | | **Read** | Read file contents | | **Write** | Create or overwrite files | | **Edit** | Find and replace in files | | **Glob** | Search files by pattern | | **Grep** | Search file contents with regex | | **WebFetch** | Fetch and read web pages | | **WebSearch** | Search the web | | **AskUser** | Ask user for input during execution | | **ToolSearch**| Search available tools | ### Advanced Tools (11) | Tool | Description | | ----------------- | ---------------------------------------------------- | | **Agent** | Spawn sub-agents (Explore, Plan types) | | **SendMessage** | Send messages between agents | | **TaskCreate** | Create tasks with descriptions | | **TaskList** | List all tasks with status filtering | | **TaskUpdate** | Update task status and owner | | **TaskGet** | Get task details by ID | | **TaskStop** | Stop a running task | | **TaskOutput** | Get output from a completed task | | **TeamCreate** | Create a team for multi-agent coordination | | **TeamDelete** | Delete a team and clean up resources | | **NotebookEdit** | Edit Jupyter notebook cells | ### Specialist Tools (13) | Tool | Description | | -------------------- | ---------------------------------------------------- | | **WorktreeEnter** | Enter an isolated worktree workspace | | **WorktreeExit** | Exit and optionally remove a worktree | | **PlanEnter** | Enter plan mode for structured planning | | **PlanExit** | Exit plan mode and return to execution | | **CronCreate** | Schedule a recurring task | | **CronDelete** | Delete a scheduled task | | **CronList** | List all scheduled tasks | | **RemoteTrigger** | Trigger a remote webhook or event | | **LSP** | Language Server Protocol integration | | **Config** | Read and write SDK configuration values | | **TodoWrite** | Manage todo lists with priorities | | **ListMcpResources** | List available MCP server resources | | **ReadMcpResource** | Read a specific MCP resource | ### Self-Evolution The SDK can automatically learn from conversations and evolve its behavior over time: **Memory Extraction** — Extract structured experience signals from conversations: ```swift let extractor = LLMExperienceExtractor(client: myLLMClient) let result = try await extractor.extract( from: messages, config: ExtractionConfig() ) for signal in result.signals { // Save to FactStore for cross-session knowledge try await factStore.save(signal: signal) } ``` **Skill Evolution** — Adapt skills based on usage signals: ```swift let evolver = LLMSkillEvolver(client: myLLMClient) let evolved = try await evolver.evolve( skill: mySkill, signals: usageSignals, config: SkillEvolutionConfig() ) ``` **Background Review Agent** — Fork a review agent after each session: ```swift let orchestrator = ReviewOrchestrator( scheduleConfig: ReviewScheduleConfig(), factStore: factStore, skillRegistry: skillRegistry, skillEvolver: evolver, usageStore: usageStore ) // Check if review is due and execute let (doMemory, doSkills) = orchestrator.shouldReview( sessionId: session.id, messageCount: messages.count, config: ReviewAgentConfig() ) if doMemory || doSkills { let reviewResult = await orchestrator.executeReview( parentAgent: agent, messages: messages, config: ReviewAgentConfig() ) } ``` **Intelligent Curation** — LLM-driven skill library maintenance: ```swift let curator = IntelligentCurator( skillCurator: SkillCurator(usageStore: usageStore, curatorStore: curatorStore), factStore: factStore, skillRegistry: skillRegistry, skillEvolver: evolver, usageStore: usageStore, curatorStore: curatorStore ) // Run two-phase curation: mechanical transitions + LLM consolidation let result = try await curator.execute(parentAgent: agent) // Generate reports let report = CuratorRunReport(from: result) print(report.renderMarkdown()) // Human-readable print(report.renderYAML()) // Machine-readable ``` ## Architecture ```mermaid graph TD A["Your Application
import OpenAgentSDK"] --> B B["Agent
prompt() / stream()
Session, Hooks, Permissions"] --> C C["Agentic Loop
API call → tools → repeat"] --> D C --> E C --> F C --> G C --> H C --> I C --> J C --> K C --> L D["LLMClient Protocol
AnthropicClient · OpenAIClient"] E["34 Built-in Tools
Core 10 · Advanced 11 · Specialist 13"] F["MCP Servers
stdio · SSE · HTTP · In-Process"] G["Session Store
JSON Persistence · Fork · Restore"] H["Hook Registry
20+ Lifecycle Events"] I["HTTP API Server
REST + SSE · Run Tracking"] J["Cost & Trace
Budget Control · JSONL Traces"] K["Self-Evolution
ExperienceExtractor · SkillEvolver
ReviewOrchestrator · IntelligentCurator"] L["Runtime Event Layer
EventBus · 18 Event Types
SSE Bridge · Token Streaming"] style A fill:#0277bd,stroke:#01579b,color:#fff,stroke-width:2px style B fill:#ef6c00,stroke:#e65100,color:#fff,stroke-width:2px style C fill:#6a1b9a,stroke:#4a148c,color:#fff,stroke-width:2px style D fill:#2e7d32,stroke:#1b5e20,color:#fff,stroke-width:2px style E fill:#b71c1c,stroke:#7f0000,color:#fff,stroke-width:2px style F fill:#00695c,stroke:#004d40,color:#fff,stroke-width:2px style G fill:#4a148c,stroke:#4a148c,color:#fff,stroke-width:2px style H fill:#e65100,stroke:#bf360c,color:#fff,stroke-width:2px style I fill:#1565c0,stroke:#0d47a1,color:#fff,stroke-width:2px style J fill:#880e4f,stroke:#560027,color:#fff,stroke-width:2px style K fill:#00695c,stroke:#004d40,color:#fff,stroke-width:2px style L fill:#283593,stroke:#1a237e,color:#fff,stroke-width:2px ``` ## Environment Variables | Variable | Description | | -------------------- | -------------------------------------------------- | | `CODEANY_API_KEY` | API key (required) | | `CODEANY_MODEL` | Default model (default: `claude-sonnet-4-6`) | | `CODEANY_BASE_URL` | Custom API endpoint for third-party providers | ## Documentation API documentation and guides are available via Swift-DocC: - [Getting Started](Sources/OpenAgentSDK/Documentation.docc/GettingStarted.md) — 15-minute walkthrough - [Tool System](Sources/OpenAgentSDK/Documentation.docc/ToolSystem.md) — Tool protocol, custom tools, tiers - [Multi-Agent Orchestration](Sources/OpenAgentSDK/Documentation.docc/MultiAgent.md) — Sub-agents, teams, tasks - [MCP, Sessions & Hooks](Sources/OpenAgentSDK/Documentation.docc/MCPSessionHooks.md) — MCP integration, persistence, hook system - [Cookbook](docs/cookbook.md) — 18 real-world scenarios with runnable code (structured output, sandbox, multi-agent, self-evolution, runtime events, etc.) - [Runnable Examples](Examples/README.md) — 47 runnable example targets with 38 tutorial sections, including Claude Code compatibility, EventBus, MCP, skills, and SDK parity demos ## Requirements - Swift 6.1+ - macOS 13+ ## Development ```bash # Build swift build # Run tests swift test # Open in Xcode open Package.swift ``` ## Acknowledgments This project is inspired by [open-agent-sdk-typescript](https://github.com/codeany-ai/open-agent-sdk-typescript), which provides the same agentic architecture for the TypeScript/Node.js ecosystem. ## Star History Star History Chart ## License MIT