Ed for Swift
Ed is a private, on-device agent for Swift apps. Onde runs the language model; Ed gives that model a safe way to ask your app to do things.
It runs on iPhone, iPad, Mac, Apple TV, Apple Vision Pro, and Apple Watch. Model input, conversation history, and tool results stay on the device unless a tool you write deliberately sends data elsewhere.
The mental model
If you are new to agents, think of Ed as a loop:
- Your app sends a message to the model.
- The model either answers, or requests one of your registered tools.
- Ed checks the tool's risk level.
- Your Swift code runs the approved tool and returns text.
- The model reads that result and writes the final answer.
The model cannot call arbitrary Swift APIs. It only sees tools your app registers.
Installation
In Xcode, choose File > Add Package Dependencies and enter:
https://github.com/ondeinference/ed-swift
Then add the Ed product to your target and import it:
import Ed
Quick start
First, describe a tool and implement it. A JSON schema tells the model which arguments it may provide:
import Ed
struct BatteryTool: EdTool {
let definition = EdToolDefinition.readOnly(
name: "battery_level",
description: "Read the device battery percentage",
parametersSchema: #"{"type":"object","properties":{}}"#
)
func execute(arguments: String) async throws -> String {
// Read the real value from your platform API here.
"The battery is at 73 percent."
}
}
Then create the agent, register the tool, and load the default model:
let ed = EdAgent { call in
// This callback is used only for mutating tools.
let userApproved = await showApproval(for: call)
return userApproved ? .allowOnce : .deny
}
try await ed.register(BatteryTool())
try await ed.loadDefaultAgentModel(
systemPrompt: "You are a concise assistant inside my app."
)
let reply = try await ed.run("How much battery do I have?")
print(reply.text)
Use .readOnly for tools that only inspect state. Use .mutating for tools
that save, send, purchase, delete, unlock, or otherwise change something. Ed
runs read-only tools automatically and sends every mutating call through your
approval callback.
Observe progress and tool activity with an async stream:
for await event in ed.events() {
print(event)
}
Plain chat, streaming and saved conversations
For chat without tools, stream yields the reply as it is generated. Breaking
out of the loop or cancelling the task stops generation:
for try await delta in ed.stream("Summarise this thread.") {
reply += delta
}
Other calls for apps with their own chat history:
await ed.restoreHistory(savedMessages) // switch to a saved chat (needs a loaded model)
let draft = try await ed.generate(messages: messages) // one-shot, history untouched
await ed.setSystemPrompt("Be brief.")
await ed.setSampling(EdSamplingConfiguration(temperature: 0.2))
EdAgent(appID:) reports usage under your Onde app id. Pass a two-argument
approval handler ({ call, risk in ... }) to learn whether a call is read-only
or mutating, and watch for EdEvent.textDelta while run generates.
Cancelling the Swift task also cancels the active Ed turn:
let task = Task { try await ed.run("Do the task") }
task.cancel()
See Examples/EdExample for a complete SwiftUI example
with a read-only device tool and an approval-gated mutating tool.
Local development
Keep onde, ed, and ed-swift next to one another, then build a
local framework before running Swift tests:
make macos
swift test
Use make ios, make tvos, make visionos, or make watchos for another
Apple platform. The package automatically uses EdFramework.xcframework when
that local file exists. Without it, SwiftPM downloads the signed release asset.
Licence
Ed is available under either the MIT License or Apache License 2.0.