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.
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.
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 EdFirst, 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)
}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.
Keep onde, ed, and ed-swift next to one another, then build a
local framework before running Swift tests:
make macos
swift testUse 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.
Ed is available under either the MIT License or Apache License 2.0.