Agents
An agent combines a model, tools, and a system prompt into an autonomous reasoning loop.
The Agent Loop
When you call agent.run(), the agent enters a loop:
The loop is bounded by maxCycles (default: 10) to prevent runaway execution.
Creating an Agent
The Agent initializer accepts a model provider and optional configuration:
let agent = Agent(
model: provider,
tools: [myTool, anotherTool],
systemPrompt: "You are a helpful assistant specializing in Swift development.",
maxCycles: 10
)
System Prompt
The system prompt defines the agent's persona, constraints, and behavior. It is prepended to every conversation and never shown to the user.
// A focused agent that only answers questions about Swift
let swiftAgent = Agent(
model: provider,
systemPrompt: """
You are a Swift language expert. Answer only Swift-related questions.
Provide code examples whenever helpful. Use Swift 6 concurrency patterns.
"""
)
// A task-completing agent with tools
let assistantAgent = Agent(
model: provider,
tools: [searchWeb, readFile, writeFile],
systemPrompt: "You are a productive assistant. Complete tasks step by step using your tools."
)
Running an Agent
Basic run
agent.run() blocks until the agent produces a final response and returns an AgentResult:
let result = try await agent.run("Explain async/await in Swift")
print(result.output) // the final text response
print(result.usage.inputTokens) // tokens consumed
print(result.metrics.cycleCount) // how many reasoning cycles ran
Streaming run
agent.stream() returns an AsyncStream<StreamEvent> so you can react to each token as it arrives:
for try await event in agent.stream("Write a README for a Swift package") {
switch event {
case .textDelta(let token):
print(token, terminator: "") // stream to UI
case .toolUse(let use):
print("\n[calling \(use.name)...]")
case .toolResult(let res):
print("[result: \(res.status)]")
case .result(let final):
print("\n\nFinished: \(final.metrics.totalLatencyMs)ms")
default:
break
}
}
Conversation History
Agents maintain conversation history across calls within the same instance. Each run() appends to the existing message history:
let agent = Agent(model: provider)
_ = try await agent.run("My name is Alex.")
let result = try await agent.run("What's my name?")
print(result.output) // "Your name is Alex."
// Inspect or clear history
print(agent.messages.count)
agent.clearHistory()
Structured Output
Use runStructured() when you need the model to return a specific Swift type instead of free-form text. See the Structured Output page for details.
@StructuredOutput
struct WeatherReport {
let city: String
let temperature: Double
let condition: String
}
let report: WeatherReport = try await agent.runStructured("Weather in London?")
print(report.temperature) // 14.5
Hooks
Hooks let you observe or intercept agent lifecycle events without modifying the agent itself:
// Log before each model invocation
agent.hookRegistry.addCallback(BeforeInvocationEvent.self) { event in
print("Sending \(event.messages.count) messages to model")
}
// Capture metrics after each run
agent.hookRegistry.addCallback(MetricsEvent.self) { event in
print("Cycles: \(event.metrics.cycleCount), Tokens: \(event.metrics.totalUsage.totalTokens)")
}