Composable on-device + remote agent runtime for Apple platforms. Native Swift agents, workflows, skills, JS plugins, MCP — FoundationModels / MLX / OpenAI / Anthropic / Gemini.
SaferSkills independently audited aria (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Composable on-device + remote agent runtime for Apple platforms.
Aria is a Swift library for building agent-driven applications. The default path runs on-device using Apple's FoundationModels, MLX, or Core ML; the same Agent and WorkflowKit surfaces also drive remote OpenAI / Anthropic / Gemini / OpenAI-compatible providers (see Remote-LLM orchestration below for the current limitations). Aria provides a tool-calling agent runtime, type-safe abstractions over LLMs, memory primitives, a workflow runtime with native + JS-plugin capabilities, an Anthropic- style skill system, and an optional graph orchestration layer.
The core is platform-agnostic and builds on Linux. Apple-specific implementations live in AriaApple.
Status: Layers 1–6 are implemented and tested on iOS 26 / macOS 26 / Linux. Headline features:
>
- Tool-calling Agent with streaming events, the full middleware stack (history persistence, windowing, summarization, RAG retrieval, automatic fact extraction), and provider-agnostic structured-outputrespond(_:as:)that works against FoundationModels and any cloudLLMProvider. - WorkflowKit runtime — Codable workflow model, GRDB persistence, compile-to-StateGraphengine, capability broker for native iOS frameworks, JS plugin steps, per-step server-LLM routing, MCP integration, skill resolution. Seedocs/workflowkit.md. - AgentKit runtime — CodableAgentDefinition, file-per-row JSON store, capability-to-tool bridging,ProposeTool+AgentApprovalSinkfor human-in-the-loop side-effects, checkpoint middleware, in-loop validator with retry cap. Apple-only; injects the host's MCP / workflow / plugin / skill tool surfaces via closures. Seedocs/agentkit.md. - Skills — Anthropic-style instruction bundles (SKILL.mdfrontmatter + body) withSkillProvider, on-demandload_skilltool, per-thread / per-workflow overrides viaSkillOverridesStore. Seedocs/skills.md. - JS plugin tools — sandboxedJSContext-based runtime (AriaToolsJS) that loads.aria-toolbundles. Capabilities (HTTP, JSON, clipboard, share, notify, storage) are gated by per-bundle manifest declarations and enforced at bridge-construction time. Seedocs/plugins.md. - Native capabilities — Keychain-backed secrets, Calendar / Reminders, HealthKit, CoreLocation, EventKit, Files, Clipboard, Share, Notifications, HTTP, Focus, Shortcuts. Wrapped behind aCapabilityBrokerthat enforces per-plugin grants. - StateGraph with conditional edges, parallel branches + reducers, agent-as-node helpers, and resumable runs via theCheckpointer. - Memory layer with persistent SQLite chat history + vector store (GRDBChatHistory,GRDBVectorStore),NLEmbeddingEmbedderfor on-device embeddings, andHistoryRetentionPolicyfor bounded disk growth. - Long-thread strategies that just compose:HistoryWindowMiddleware(turns + token caps),HistorySummarizationMiddleware(compress older portion into a summary system message),FactExtractionMiddleware(auto-mine durable user facts intoMemoryStore). - OpenTelemetry-compatible observability viaswift-distributed-tracing+swift-metrics. Spans use OTel GenAI semantic conventions; backends like Phoenix / Honeycomb auto-render the runs. - Session recording + replay viaSessionRecorder+SessionReplayer(inAriaTesting). Capture a run as aSessionBundleJSON, ship it anywhere, replay against a fresh agent for regression tests, prompt experiments, or debugging.
[String: Any].AsyncThrowingStream is the universal output shape; SwiftUI integration is trivial. ┌────────────────────────────────────────────────────────┐
│ HOST APP (avyra, niora, your app) │
│ Wires tool sources + LLM routing into the runtimes, │
│ ships UI, owns content (workflows, agents, skills) │
└──┬───────────────────────────────┬─────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────┐
│ WorkflowKit │ │ AgentKit │
│ ── recipes you write │ │ ── goals you delegate │
│ • Codable Workflow │ │ • Codable AgentDefinition│
│ • CapabilityBroker │ │ • AgentRuntime + compiler│
│ • Step compiler → │ │ • ProposeTool + approval │
│ StateGraph │ │ • Checkpoint middleware │
│ • GRDB store │ │ • File-JSON stores │
└──────────┬───────────┘ └────────────┬──────────────┘
│ │
└─────────────┬────────────────────┘
▼
┌────────────────────────────────────────────────────────┐
│ Aria (core) │
│ Layer 6: StateGraph (optional graphs) │
│ Layer 5: Agent (tool-calling loop) │
│ Layer 4: Memory (history, vectors) │
│ Layer 3: Providers (LLM, Tool, Embedder) │
│ Layer 2: Runnable (composability) │
│ Layer 1: Foundation (data model) │
└────────────────────────────────────────────────────────┘
▲
│ binds to platform-specific impls
┌────────────────────────┴───────────────────────────────┐
│ AriaApple AriaMLX* AriaVoice AriaVoiceKokoro* │
│ ────────── ──────── ───────── ───────────────── │
│ FoundationModels Speech + AVSpeech Kokoro 82M │
│ GRDB memory STT/TTS TTS │
│ (* = SPM-trait-gated) │
└────────────────────────────────────────────────────────┘Mental model: the bottom (Aria core) is platform-agnostic and Linux-buildable; the platform tier binds it to Apple APIs; WorkflowKit + AgentKit are two peer runtimes sitting above that — WorkflowKit runs recipes you wrote, AgentKit runs goals you delegated. Both compose Aria's Agent loop + middleware; both call into CapabilityBroker for native side effects. Host apps pick either or both.
Within Aria, a layer depends only on layers below. See docs/architecture.md.
Sources/
├── Aria/ Layers 1–6, platform-agnostic (Linux-buildable)
├── AriaTesting/ Mocks, fixtures, SessionReplayer
├── AriaApple/ FoundationModels + GRDB-backed memory (Apple-only)
├── AriaTools/ Cross-platform tool implementations (HTTP, JSON, Regex, …)
├── AriaToolsJS/ JavaScriptCore-sandboxed user plugin runtime
├── AriaVoice/ Speech.framework STT + AVSpeechSynthesizer TTS
├── AriaMLX/ MLX-backed LLMProvider (opt-in via `MLX` trait)
├── AriaVoiceKokoro/ On-device Kokoro 82M TTS (opt-in via `VoiceKokoro` trait)
├── WorkflowKit/ Workflow runtime + CapabilityBroker + skills + plugin steps
├── AgentKit/ Agent runtime + AgentDefinition + ProposeTool + checkpoints
└── AriaCLI/ Demo CLI — records a run, prints the bundle, replays itWorkflowKit and AgentKit are independent of each other; either can be used standalone. The avyra app ships both — workflows for deterministic tile-tap tasks, agents for delegated goals.
Aria exposes two SPM traits (SE-0480, Swift 6.1+) for code paths with large transitive dependency graphs:
| Trait | Pulls in | Enables |
|---|---|---|
MLX | mlx-swift-lm, swift-huggingface-mlx, swift-transformers-mlx | AriaMLX — on-device LLM via MLX |
VoiceKokoro | kokoro-ios, mlx-swift, MLXUtilsLibrary | AriaVoiceKokoro — Kokoro 82M TTS |
Neither trait is on by default. SwiftPM still resolves the dependency packages (they appear in Package.resolved), but the target sources compile to empty unless the matching trait is enabled — so the heavy build / link cost only kicks in when a consumer actually uses the feature.
Enable from a consumer's Package.swift:
.package(
url: "https://github.com/prasadpamidi/aria.git",
from: "0.1.0",
traits: ["MLX", "VoiceKokoro"]
)Or in an Xcode project's "Add Package Dependency" dialog — tick the trait checkboxes when adding Aria.
The Fastfile wraps every trait combination:
bundle exec fastlane package_build # default traits
bundle exec fastlane package_build_mlx # --traits MLX
bundle exec fastlane package_build_voice_kokoro # --traits VoiceKokoro
bundle exec fastlane package_build_all # --traits MLX,VoiceKokoro
bundle exec fastlane package_tests # swift test
bundle exec fastlane quality # swiftformat + swiftlintimport Aria
import AriaApple
let storage = try GRDBStorage()
let timeKit = registerFoundationModelsTool(CurrentTimeTool())
let agent = Agent(config: AgentConfig(
provider: FoundationModelsProvider(typedTools: [timeKit.factory]),
tools: [timeKit.anyTool],
systemPrompt: "You are a helpful assistant.",
threadId: "main",
middleware: [HistoryMiddleware(history: storage.chatHistory)]
))
for try await event in agent.stream(.message(.user("What's the time in Tokyo?"))) {
switch event {
case let .textDelta(chunk): print(chunk, terminator: "")
case let .toolCallRequested(call): print("[calling \(call.name)]")
case .finish: print("\n[done]")
default: break
}
}Long-running threads need bounded context, a summary of older turns, and a durable memory layer. Compose the built-in middlewares — order matters (load → summarize → window → recall → extract):
import Aria
import AriaApple
let storage = try GRDBStorage()
let embedder = NLEmbeddingEmbedder()!
let memory = DefaultMemoryStore(
embedder: embedder,
store: storage.vectorStore(dimensions: embedder.dimensions)
)
let agent = Agent(config: AgentConfig(
provider: FoundationModelsProvider(),
tools: [],
systemPrompt: "You are a helpful assistant.",
threadId: "main",
middleware: [
// 1. Persistent transcript loaded from GRDB on beforeRun
HistoryMiddleware(history: storage.chatHistory),
// 2. Compress the older portion into a summary system message
// once the thread grows past 24 non-system turns
HistorySummarizationMiddleware(
triggerAfterTurns: 24,
keepRecentTurns: 6,
summarizer: { messages in
// Typically a cheap LLM call (gpt-4o-mini, on-device 3B)
try await mySummarizer.summarize(messages)
}
),
// 3. Hard window so the provider sees a bounded transcript
HistoryWindowMiddleware(maxTurns: 16, maxTokens: 4000),
// 4. Recall top-K user memories for the latest message
RAGMiddleware(memoryStore: memory, namespace: ["user", userId], topK: 5),
// 5. Auto-mine durable facts from each user turn (background)
FactExtractionMiddleware(
memory: memory,
namespace: ["user", userId],
extractor: { message in
try await myExtractor.facts(from: message)
}
),
]
))
// Bound disk growth across all threads — run on launch / nightly.
try await HistoryRetentionPolicy(maxThreadAgeDays: 90, maxThreadCount: 20)
.enforce(on: storage.chatHistory)Agent.respond(_:as:) works against FoundationModels and any cloud LLMProvider — the agent layer derives the schema from the Generable type and injects it as ResponseFormat.schema(...) (or .rawSchema(...) for opaque JSON Schemas that don't round-trip through Aria's typed JSONSchema):
@Generable
struct ActivitySuggestion {
var title: String
var summary: String
var steps: [String]
}
for try await event in agent.respond(.message(.user("Suggest something fun")),
as: ActivitySuggestion.self) {
switch event {
case .partial(let snapshot): render(snapshot) // optionals fill in over time
case .finish(let suggestion): commit(suggestion)
case .toolCallExecuted: break
}
}A complete record + replay loop in 20 lines (cross-platform, no Apple deps):
import Aria
import AriaTesting
// 1. Record an agent run
let recorder = SessionRecorder()
let recording = RecordingMiddleware(recorder: recorder)
let provider = MockLLMProvider(scenes: [.text("hello")])
let agent = Agent(config: AgentConfig(
provider: provider, tools: [], threadId: "demo",
middleware: [recording]
))
for try await _ in agent.stream(.message(.user("hi"))) {}
// 2. Bundle it (Codable, ship anywhere)
let bundle = await recorder.bundle()
let json = try JSONEncoder().encode(bundle)
// 3. Replay against a fresh agent
let replay = SessionReplayer.mockProvider(from: bundle.agent!)
let replayedAgent = Agent(config: AgentConfig(provider: replay, tools: [], threadId: "replay"))Run the same flow as a CLI demo:
swift run AriaCLI # records, prints the JSON bundle, replays itAria's Agent and WorkflowKit runtime accept any LLMProvider conformer — the on-device FoundationModels / MLX providers are the default path, but cloud OpenAI / Anthropic / Gemini / OpenAI-compatible endpoints work the same way. A workflow step declares serverProviderID: "openai-prod" (or leaves it nil for the default provider) and the runtime resolves the right transport at compile time.
What works today:
respond(_:as:) against anyLLMProvider — FoundationModelsProvider, MLXProvider, and the server-LLM resolver path in WorkflowKit cover the same agent + middleware stack.
ServerLLMProviderResolver so aworkflow can mix on-device steps with cloud steps in one graph.
CredentialStore so each serverprovider's API key + auth headers come from Keychain, not config files.
Known limitations / pending work:
OpenAI-compatible providers but the typed-tool surface still routes most cleanly through FoundationModels' @Generable path on Apple platforms. Cloud-provider tool calls go through the same Agent runtime but the JSON-schema → @Generable round-trip is opaquer; complex multi-tool runs may need a schema-level adapter per provider.
data: SSE vs Anthropic's event-typed SSE vs Gemini's protobuf responses). Aria normalizes to a single ProviderEvent stream; edge cases like mid-stream tool calls in newer Anthropic tool_use events are still being hardened.
limiting / retry behaviour across MCP transports (stdio, http) is a per-server contract that callers wire themselves.
details in some failure modes. Treat ProviderError as a hint, not a stable contract.
See docs/workflowkit.md for the routing / resolver shape and a worked OpenAI + on-device hybrid example.
swift build # build the package (default traits)
swift build --traits MLX,VoiceKokoro # build with the heavy traits
swift test # run all tests
swift test --filter AriaTests # core tests (Linux-safe)
swift run AriaCLI # run the CLI demobrew bundle # swiftformat, swiftlint
bundle install # fastlane
./scripts/install-hooks.sh # pre-commit hook (lint + format gate)Routine work goes through Fastlane lanes:
bundle exec fastlane package_build # swift build (default traits)
bundle exec fastlane package_build_all # all trait variants
bundle exec fastlane package_tests # swift test
bundle exec fastlane cli_demo # swift run AriaCLI
bundle exec fastlane lint # SwiftLint
bundle exec fastlane format # SwiftFormat (in place)
bundle exec fastlane quality # format --lint + lint
bundle exec fastlane quality fix:true # auto-fix bothSee AGENTS.md for the full lane reference.
docs/overview.md — what Aria is, who it's fordocs/principles.md — architectural principlesdocs/architecture.md — layered design and module layoutdocs/traits.md — SPM traits (MLX, VoiceKokoro)docs/workflowkit.md — workflow runtime, capabilities, server-LLM routingdocs/agentkit.md — agent runtime, definitions, propose-then-host-executesdocs/skills.md — Anthropic-style skill bundlesdocs/plugins.md — JS plugin tools (.aria-tool runtime)docs/platform-boundary.md — cross-platform disciplinedocs/observability.md — OTel tracing/metrics + session recording / replaydocs/layers/ — per-layer specsdocs/decisions/ — architecture decision recordsdocs/glossary.md — terminologySee CONTRIBUTING.md and AGENTS.md. Aria is a clean-room implementation; please read NOTICE.md before contributing.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.