Skip to content

About

Composable AI workflows for Swift: small typed steps, a model per step, tools, MCP and on-device models.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

33 Commits

Folders and files

Repository files navigation

ModelRelay: Composable AI workflows for Swift

Swift 6.4 iOS 18+, macOS 15+ and visionOS 2+ Swift Package Manager compatible MIT license

ModelRelay

Small steps. Different models. One workflow.

An AI feature often needs more than one prompt. Read a receipt, extract its fields, check the numbers, ask a tool, then let the user review the result.

ModelRelay helps you connect those steps in Swift. Choose a model for each task, pass typed results between them, and keep control over tools, permissions and execution.

Built on AnyLanguageModel for inference and conversations. ModelRelay adds the workflow around them. Your app brings the interface, data and storage.

Let's build something ๐Ÿ™‚

๐ŸŒŸ Features ยท ๐Ÿค” Why ModelRelay? ยท ๐Ÿ“ฒ Installation ยท ๐Ÿ’ Usage ยท ๐Ÿง  Models ยท ๐Ÿ’ก Use cases ยท ๐Ÿ›  Advanced ยท ๐Ÿ“š Guides ยท ๐Ÿค– AI agents ยท ๐Ÿ“ฑ Runline app ยท โค๏ธ Contributing

๐ŸŒŸ Features

  • ๐Ÿงฉ Compose in Swift: small steps, result builders, branches and parallel work.
  • ๐Ÿง  Choose models per step: Apple models, cloud providers, OpenAI-compatible servers and opt-in local MLX.
  • ๐Ÿค Connect agents: share a native conversation or hand selected results to another model.
  • ๐Ÿงฐ Bring your tools: HTTP APIs, MCP tools and reusable tool kits.
  • ๐ŸŽฏ Ask typed questions: extract fields, classify text or use decision models such as Jev.
  • ๐Ÿ‘จโ€๐Ÿ’ป Add your own logic: async Swift bodies, without a script runner.
  • ๐Ÿ”’ Set boundaries: write approval, local-only data policies and execution budgets.
  • โธ Review and resume: checkpoint contracts, approval gates, step records and metadata-only events.

An app passes data through extraction, a tool-enabled agent and custom Swift, then receives typed output. AnyLanguageModel powers the model steps.

๐Ÿค” Why not just use Apple's APIs?

You can! Foundation Models already provides stateful sessions and streaming, structured generation and tool calling. For a prompt, an extraction or a conversation with Apple's model, using it directly is often the simplest choice.

ModelRelay is for the process around those calls:

You needโ€ฆ With Apple APIs directly ModelRelay adds
๐Ÿงฉ Multiple steps Connect calls with Swift concurrency and your own state Named results, branches and parallel blocks in one Workflow
๐Ÿง  Different models Integrate providers and choose when to call each Per-step catalog IDs and one explicit fallback, using AnyLanguageModel providers
๐Ÿค Shared context Keep sessions and pass selected results yourself Named conversations and explicit handoffs between agents
๐Ÿงฐ External tools Implement Tool types and service integrations Reusable kits, HTTP/MCP integrations and write approval
๐Ÿ”’ Execution rules Add application-level checks and counters Preflight validation, privacy labels and workflow-wide attempt/concurrency limits
โธ Review and recovery Persist transcripts plus your own job state Approval gates and start / resume checkpoints with a host-owned store
๐Ÿ“ก Progress Stream generated content and inspect session history Workflow events, step records and a final execution report

Apple also supports custom model providers on OS 27+. ModelRelay's value is reusable workflow orchestration, not exclusive access to tools, structured output or other models.

Support apps from iOS 18. ModelRelay's workflows, remote models, tools, MCP and checkpoints work on iOS 18 and later. Apple's model features still require their supported OS and hardware. Platform requirements โ†’

You could build this coordination yourself. ModelRelay packages it without replacing the native inference runtime. It does not make Apple's model faster or remove its hardware, availability or entitlement requirements. Workflow events are not token streaming, and recovery still needs your durable storage. Execution boundaries โ†’ ยท Recovery contract โ†’

๐Ÿ“ฒ Installation

Build with Xcode 27 / Swift 6.4. Your app can target iOS 18+, macOS 15+ or visionOS 2+. This package currently targets Apple platforms, not Linux.

Capability Minimum OS
Workflows, remote models, tools, MCP and checkpoints iOS 18 / macOS 15 / visionOS 2
Apple's on-device text model and tools iOS 26 / macOS 26 / visionOS 26
Apple model image input and Private Cloud Compute iOS 27 / macOS 27 / visionOS 27

Apple models also need compatible hardware and model availability. Private Cloud Compute requires its entitlement. Use another registered provider on older systems, or configure an explicit fallback.

Add ModelRelay with Swift Package Manager:

// swift-tools-version: 6.4
import PackageDescription

let package = Package(
    name: "MyClient",
    platforms: [.macOS(.v15), .iOS(.v18), .visionOS(.v2)],
    dependencies: [
        .package(url: "https://github.com/Juanpe/ModelRelay.git", "0.1.0" ..< "0.2.0", traits: []),
    ],
    targets: [
        .target(name: "MyClient", dependencies: [
            .product(name: "ModelRelay", package: "ModelRelay"),
        ]),
    ]
)

Releases follow Semantic Versioning. During 0.x, patch updates stay compatible; breaking changes require a new minor version. The range above accepts 0.1.x, not 0.2.0. Changelog โ†’ ยท Version policy and publishing โ†’

๐Ÿ“ฆ Which products do I need?

One package, separately importable products. Start with the core and add integrations when you need them:

Product What it adds
ModelRelay Workflows, typed values, builders, execution and custom Swift bodies
ModelRelayHTTP HTTP request steps and HTTP agent tools through URLSession
ModelRelayCloud Specialized decision models, including Jev
ModelRelayMCP MCP tools over Streamable HTTP and optional OAuth helpers
ModelRelayMLX Convenience presets for native MLX providers

For ordinary cloud generation, register AnyLanguageModel's providers directly. ModelRelayCloud is for specialized decisions, not a required cloud adapter. Apple's on-device model, Private Cloud Compute and text recognition are built in; register other providers in your ModelCatalog.

ModelRelay has one direct third-party package dependency: AnyLanguageModel. Selecting fewer products avoids building unused targets, but SwiftPM may still resolve optional transitive dependencies.

Using MLX? Add the ModelRelayMLX product and change traits: [] to traits: ["MLX"]. It is disabled by default and also needs Xcode's Metal Toolchain (xcodebuild -downloadComponent MetalToolchain).

๐Ÿ’ Usage

Start with three steps:

1๏ธโƒฃ Import

import ModelRelay

2๏ธโƒฃ Describe your workflow

Let's turn a receipt into typed fields:

let workflow = Workflow(
    name: "Receipt to expense"
) {
    Step.extract("expense", instruction: "Extract the expense from this receipt.", model: .appleOnDevice) {
        Field("merchant", .text)
        Field("total", .number)
        Field("currency", .choice(["EUR", "USD", "GBP"]))
    }
}

name is the workflow's display name. Step names identify results that later steps can reference.

3๏ธโƒฃ Run it

let report = try await WorkflowExecutor().run(
    workflow,
    input: .text("BLUE BOTTLE COFFEE ยท TOTAL EUR 8.30")
)

print(report.output["total"]?.textValue ?? "") // e.g. 8.3

That's it! Your app receives a Value, not a string it has to parse by hand.

๐Ÿ’ก This example uses Apple's on-device model, which must be available on your device. Other models and fallbacks need to be registered in a ModelCatalog first.

See models and setup for Jev, Ollama and other providers.

Want to try it without a model or API key? The standalone example runs a deterministic workflow:

swift run --package-path Examples/StandaloneClient StandaloneClient

๐Ÿงฉ The main pieces

You want toโ€ฆ Start here
Describe the process Workflow, Step, Field
Pass data in and get it back Value, WorkflowExecutionReport
Choose model implementations ModelCatalog
Execute a workflow WorkflowExecutor.run
Save progress and wait for review start / resume, WorkflowExecutionCheckpointStore

By default, each step reads the previous result. Select an earlier result with input: "expense", or insert one into text with {{expense.total}}. See the usage recipes for all step types and data-flow rules.

๐Ÿง  Pick your models

Use Jev to make a decision, Ollama to summarize, and another model to draft the reply. Each step chooses a registered model ID; the workflow stays the same.

Want to useโ€ฆ Registerโ€ฆ Example
๐Ÿ—ณ Jev CloudModels.jev + DecisionsAPIModel from ModelRelayCloud Route and score a ticket
๐Ÿฆ™ Ollama Native OllamaLanguageModel Summarize with Qwen
โ˜๏ธ OpenAI / Anthropic Native OpenResponsesLanguageModel / AnthropicLanguageModel Cloud providers
๐Ÿ”Œ OpenAI-compatible servers Native OpenAILanguageModel with your base URL Bring an endpoint
๐ŸŽ Apple models / OCR Built in: .appleOnDevice, .applePrivateCloud, .appleTextRecognition Use Apple's models
โšก๏ธ On-device MLX ModelRelayMLX presets with the MLX trait Run Qwen in-process

Jev answers typed questions; it is not a text generator. Ollama serves a model over HTTP; MLX runs it inside your process. Native providers come from AnyLanguageModel, without another ModelRelay adapter.

Setup and examples โ†’ ยท Mix Jev and Ollama in one workflow โ†’

๐Ÿ’ก What can you build?

Your users don't need a workflow editor. You define the process; they use the feature inside your app.

Feature A possible workflow
๐Ÿ“ฌ A support inbox Extract the request โ†’ classify urgency โ†’ look up an order with tools โ†’ hand context to a reply agent โ†’ review before sending
๐Ÿงพ An expense assistant Read a receipt image โ†’ extract totals โ†’ classify the expense โ†’ validate with Swift โ†’ review before submitting

Use a small model for triage, a different one for drafting, and your own Swift code for business rules. ModelRelay connects them; your app owns capture, review, credentials and storage.

๐Ÿ›  A little further

๐Ÿ”€ Do independent work in parallel

Summarize an article and classify its tone, then combine both results:

let workflow = Workflow(
    name: "Analyze article",
    output: "result"
) {
    Step.parallel("analysis") {
        Step.prompt("summary", "Summarize in three bullet points.", model: .appleOnDevice)
        Step.classify("tone", "Identify the tone.",
                      labels: ["positive", "neutral", "negative"], model: .appleOnDevice)
    }
    Step.template("result", "{{summary}} ยท Tone: {{analysis.tone}}")
}

output: "result" returns the value of the step named result.

Parallel children read the same incoming data, not each other's results. Use ifEquals or switchOn when the result should choose a path. Composition recipes โ†’

๐Ÿค Keep the conversation going

Give two steps the same conversation name and model to reuse their native session within one execution:

let workflow = Workflow(
    name: "Draft and review"
) {
    Step.agent("draft", "Write a short answer.", model: .appleOnDevice)
        .withContext(.conversation("writer"))
    Step.agent("review", "Review your previous answer.", input: "input", model: .appleOnDevice)
        .withContext(.conversation("writer"))
}

Switching models? Use .withContext(.handoff(["review"])) to give a fresh session selected results. Conversations are isolated between executions; a different model or fallback has its own history. Context and resource lifetimes โ†’

๐Ÿ‘จโ€๐Ÿ’ป Add a step that is just Swift

No model needed for trimming text, validating totals or applying your app's rules:

import Foundation

let workflow = Workflow(
    name: "Normalize text"
) {
    Step.custom("normalized") { context in
        .text(context.input.textValue.trimmingCharacters(in: .whitespacesAndNewlines))
    }
}

โš ๏ธ Custom bodies are trusted, compiled host code, not a sandbox for model output. They make the workflow definition non-serializable; execution checkpoints are separate data. Custom-step boundaries โ†’

๐Ÿ”’ Keep execution under control

let executor = WorkflowExecutor(limits: .init(
    maxDuration: .seconds(60),
    maxModelCalls: 8,
    maxToolCalls: 12,
    maxConcurrentSteps: 2
))

let issues = await executor.validate(workflow)
let execution = executor.execute(workflow, input: .text("Your input"))
defer { execution.cancel() }

for try await update in execution.updates {
    switch update {
    case .event(let event): print(event.kind.rawValue, event.step ?? "")
    case .step(let record): print("Finished", record.step)
    }
}

let report = try await execution.result
print(report.usage.modelCalls, report.usage.toolCalls)

Validation runs automatically before execution; call validate when you want diagnostics ahead of time. Events contain metadata, not prompts, tool arguments or results. Step records can contain user data. Only need the report? Keep using try await executor.run(...).

Writes are denied by default. Supply a WritePolicy for approval and use .localOnly, .readOnly or .noTools policies to narrow what a workflow can do. These policies cover executor-mediated work, not arbitrary host code. Model steps can try one configured fallback; HTTP requests and tool calls are not retried by the executor.

Need human review? Add .requiringApproval("Review before continuing") and use start / resume with a host-owned checkpoint store.

๐Ÿ“ฃ Timeouts and cancellation are cooperative. Checkpoints need atomic, durable compare-and-swap storage; neither checkpoints nor timeouts guarantee exactly-once writes or extra OS background time. Safety and limits โ†’ ยท Recovery contract โ†’

๐Ÿ“š Guides

Looking forโ€ฆ Read this
Jev, Ollama, Apple, cloud providers, MLX and mixed-model workflows Models and providers
Step types, branching, HTTP tools, images, MCP and typed questions Usage recipes
Entry points, responsibilities and MCP/OAuth integration Integrating ModelRelay
Shared conversations, handoffs, resource reuse and privacy Context and policies
Validation, typed errors, events and budget accounting Execution safety
Checkpoints, approval gates and interrupted operations Recoverable executions
Version numbers, compatibility and publishing Releasing ModelRelay

๐Ÿงช Try it your way

Tool Run from this checkout
A small Mac playground swift run RelayPlayground
Workflow JSON over loopback HTTP swift run relay-serve Examples/workflows --port 8787
Accuracy and latency samples swift run relay-bench apple.on-device

These commands use real models. Enable local MLX with --traits MLX before the executable name; model weights may download on first use. The HTTP server binds to 127.0.0.1. Model-free example โ†’

๐Ÿค– Build with an AI agent

Working on your app with Claude Code or another coding agent? The ModelRelay skill teaches it to add the package, read the docs of the version your app resolves, pick the right steps and keep credentials, writes and fallbacks within the SDK's contracts.

Claude Code: install it as a plugin.

claude plugin marketplace add Juanpe/ModelRelay
claude plugin install modelrelay@modelrelay

Inside a session, /plugin marketplace add Juanpe/ModelRelay works too.

Codex: add the repository as a plugin marketplace, then install ModelRelay from the Plugins Directory.

codex plugin marketplace add Juanpe/ModelRelay

Prefer plain files? Copy skills/modelrelay where your agent looks for skills, then restart it if the skill does not appear:

Agent One app All your projects
Claude Code .claude/skills/ ~/.claude/skills/
Codex .agents/skills/ ~/.agents/skills/

App folders live at the root of your app's repository. Other agents that read SKILL.md skills can use the same folder.

๐Ÿ’ก The skill is for apps that depend on ModelRelay. Contributors to this repository follow AGENTS.md instead.

๐Ÿ“ฑ Runline: the app built on ModelRelay

Runline app logo

Prefer a ready-made app? Runline is a native app for iPhone, iPad and Mac built on ModelRelay. Create model workflows visually, choose a model for each step and run them from Shortcuts.

Runline is currently under review by Apple and is not yet available on the App Store.

๐ŸŒ Website: runline.juanpecatalan.com

โค๏ธ Contributing

Found a bug? Have a workflow you'd like to build? I'd love to hear about it.

  • Open an issue with a small example.
  • Suggest an improvement or send a pull request.
  • Add a test for a fix, especially around tools, permissions or recovery.

Before sending a change, run:

make format
make check-style

The shared Swift style uses pinned SwiftFormat and SwiftLint tools, downloaded on first use on macOS. Existing lint debt has a checked-in baseline; new violations fail the check. make lint-all shows the full audit.

Run the tests with:

env -u RELAY_LIVE swift test -Xswiftc -warnings-as-errors

The default suite uses scripted models and stubbed HTTP, not real services or credentials. Live Apple-model tests are opt-in with RELAY_LIVE=1; MLX runtime/Metal support needs separate verification on supported hardware.

๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป Author

Made by Juanpe Catalรกn.

๐Ÿ‘ฎ๐Ÿป License

ModelRelay is available under the MIT license.

About

Composable AI workflows for Swift: small typed steps, a model per step, tools, MCP and on-device models.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages