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:

1. Receive input 2. Call model 3. Execute tools 4. Back to model
Repeats until the model produces a final text response (no more tool calls)
5. Return result

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:

Basic agentSwift
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.

Specialized agentsSwift
// 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:

Swift
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:

Swift
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:

Multi-turn conversationSwift
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.

Swift
@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:

Swift
// 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)")
}