Hail an agent. It answers from wherever it lives.
Hail is a Swift runtime for durable, distributed LLM agents. The name comes from hailing a ship—or a spacecraft—and inviting a reply. Agents have stable identities, can communicate across a cluster, and can recover without changing where they are addressed.
LLMs are tools, not the foundation of your product. Hail is built on open-source Swift infrastructure and keeps providers replaceable: use a hosted API, a local or self-hosted model, or add your own integration. The goal is to give small teams and individual developers the freedom to build agent products without depending on one platform vendor.
Hail uses the reactive model familiar from actors: an isolated agent owns its state, receives a message, reacts, emits durable events, and becomes idle. There is no outer while true loop for the host to keep alive. Whether the resulting product is fully automated or human-controlled is an application decision.
scoutID is the agent's stable address. A virtual distributed actor can be local, remote, idle, or recovered while callers keep using the same identity.
import DistributedCluster
import Hail
import VirtualActors
let projectRoot = "/path/to/project"
let scoutID = AgentID(role: Role("scout"), contextID: AgentContextID("mission-7"))
// The host's definition selects the provider and model.
let scoutDefinition = AgentDefinition(
role: scoutID.role,
behavior: .init(
systemPrompt: "You are a careful signal scout.",
model: "codex/gpt-5.6-sol"
),
limits: .init()
)
// The host's definitionProvider returns scoutDefinition when the agent activates.
let scout: Agent = try await system.virtualActors.getActor(
identifiedBy: VirtualActorID(rawValue: scoutID.rawValue),
dependency: AgentSpawnConfig(
agentID: scoutID,
projectRootPath: projectRoot
)
)respond sends a message instead of calling a process-local loop and waits for a typed result. Replace ChatResponse with your own Codable output type when the domain needs a stronger contract.
let reply: ChatResponse = try await scout.respond(
"Survey the area and report anything unusual."
)
print(reply.content)Conceptually, a different agent can call the scout from inside its own behavior:
// Hypothetical code for another distributed agent.
distributed actor Navigator {
let brain: Agent
let scout: Agent
func investigate() async throws -> ChatResponse {
let report: ChatResponse = try await scout.respond(
"Inspect the signal and report your findings."
)
return try await brain.respond(
"The scout reported: \(report.content). Decide what we should do next."
)
}
}Navigator addresses the scout by its virtual-actor reference, then sends the response back to its brain agent. Either agent may be on another node or activated after being idle; the calls remain the same.
AgentStream<ChatResponse> exposes the typed turn lifecycle while it runs. It lets a UI or another agent observe progress without taking ownership of the agent's loop or state.
let stream = AgentStream<ChatResponse>(actorSystem: system)
let updates = Task {
for await update in stream.updates {
switch update {
case .run(.event(.toolRequested(let call, _))):
print("Scout requested:", call.name)
case .run(.event(.responseRecorded(let response, _))):
print("Scout replied:", response.content)
default:
break
}
}
}
try await stream.send(
"Inspect the signal and explain how confident you are.",
to: scout
)
updates.cancel()An effect is anything an agent does outside its own reasoning: reading a file, writing a file, running a command, or making a network request. Hail represents that outside-world action as a typed value before performing it. FileEffect can say “read this file” or “write to this path,” but the value itself does not touch the filesystem and does not grant permission. ScopedEffect is the common protocol for these effect values.
let requestedRead = FileEffect(
path: projectRoot + "/README.md",
action: .read
)The agent may request requestedRead, but the model should not decide which files the process can access. Authority is the host's answer to “may this described effect run?” This example allows reads only inside projectRoot; the tool performs the read only after that decision is granted.
import Foundation
import HailCore
import ScopedEffect
let authority = CompositeAuthority()
.granting(
for: FileEffect.self,
decide: FileEffect.pathPolicy(scopedTo: projectRoot)
)
let file = AnyTool(FileTool(projectRoot: URL(fileURLWithPath: projectRoot)))
let contents = try await file.execute(
input: ToolArguments(json: #"{"action":"read","path":"README.md"}"#),
authority: authority
)
print(contents)Agents often need credentials when a tool performs an effect—for example, an HTTP tool calling an API. Passing the API key as a String would make it ordinary data that could be copied into prompts, events, logs, or configuration. SecretRef stores only a lookup such as an environment-variable name or secret-store ID. The agent requests the operation, the authority controls the network access, and infrastructure resolves the reference only when the tool performs the request.
// Configuration contains a reference, not the secret itself.
let apiKey = SecretRef.env("OPENAI_API_KEY")
let request = NetworkEffect(host: "api.example.com", operation: "GET /search")
let authority = CompositeAuthority()
.granting(
for: NetworkEffect.self,
decide: NetworkEffect.hostPolicy(allowing: ["api.example.com"])
)
// The host resolves apiKey inside the effect scope.
try await withEffect(request, authority: authority) { token in
try await apiClient.get(
"/search",
credential: secretStore.resolve(apiKey),
effectToken: token
)
}SecretRef provides the credential lookup; NetworkEffect and Authority provide the access boundary. The raw secret exists only inside the infrastructure that performs the request.
The host supplies definitions, tools, and policy as values. This keeps product decisions—automation, tools, and trust—outside the actor runtime and explicit at composition time.
let runtime = AgentRuntimePlugin(
definitionProvider: { spawn in
await definitions.lookup(spawn.agentID.role)
},
extraToolsProvider: { agentID, sourceAgentID, system in
makeHostTools(for: agentID, calledBy: sourceAgentID, in: system)
},
authorityProvider: { spawn in
CompositeAuthority.workspaceDefault(
root: spawn.scope.path ?? spawn.projectRootPath
)
}
)An agent is an isolated stateful actor with a typed message boundary. It does not belong to one process or machine, and Distributed Actors make remote calls explicit. The application communicates with an agent rather than managing its mailbox or lifecycle directly.
Virtual Actors give agents durable logical identity: they activate an entity when a message arrives and passivate it when idle. Event Sourcing records meaningful transitions—messages, turn boundaries, model sessions, tool activity, and responses—so a new actor instance can replay the journal and continue after failure. The journal is the source of truth; a provider's warm session is only an optimization.
An LLM can propose an action, but the host must decide what that action may touch. ScopedEffect represents file, process, and network operations as typed values. An Authority decides whether a concrete effect is allowed, and withEffect creates a non-escapable EffectToken only inside the granted scope. Authorities can be composed for workers, reviewers, trusted hosts, or untrusted turns.
This design is inspired by Martin Odersky and collaborators' work on tracked capabilities for safer agents, adapted to Swift's current runtime and concurrency capabilities.
Hail is agnostic about roles, prompts, workflows, tools, persistence layout, and UI. The host supplies definitions, context, tools, and policy as explicit values.
Models are equally open. A provider implements ModelEndpoint and can be hosted, local, self-hosted, or custom. Hail includes Kimi Code over ACP, Claude Code over its stream-JSON CLI, Codex over its native App Server protocol, and API-backed endpoints, but no provider is privileged by the architecture. Session-owning endpoints may keep warm conversations; stateless endpoints receive the assembled transcript. In both cases, the event journal remains the recovery authority.
Depend only on the layer you need:
| Product | Purpose |
|---|---|
ScopedEffect |
Capability scopes, authority decisions, auditing, and secret vocabulary |
HailCore |
Agent domain values, model contracts, tools, and effect descriptors |
HailModelRuntime |
Model routing plus Kimi, Claude, Codex, and API endpoint implementations |
HailAgentRuntime |
Distributed, virtual, event-sourced agents and the reasoning runtime |
Hail |
Umbrella product that re-exports the complete stack |
- Swift 6.4 package tools
- macOS 26 for local CLI model harnesses
The current implementation is built on AnyLanguageModel for model abstractions and swift-acp for Agent Client Protocol integrations. These are implementation foundations and may change as Hail evolves; model providers remain replaceable.
Hail is licensed under the Apache License 2.0. See LICENSE.txt and NOTICE.txt.
Run the suite with:
swift test