Agents Overview
Agents are the unit of work in AgentSquad. Each agent owns its LLM call and tool-use loop; the Orchestrator decides which agent handles a given turn and manages chat persistence.
AgentProtocol
Section titled “AgentProtocol”Every agent conforms to AgentProtocol:
public protocol AgentProtocol: Sendable { var id: String { get } // storage namespace + classifier key; defaults to slugify(name) var name: String { get } var description: String { get } var saveChat: Bool { get } // orchestrator persists turns; defaults to true var maxToolRounds: Int { get } // tool-loop cap; defaults to 1
func process( _ input: AgentInput, history: [ConversationMessage], context: AgentContext ) -> AsyncThrowingStream<AgentEvent, any Error>}The protocol extension provides default implementations for id, saveChat, and maxToolRounds, so a minimal custom agent only needs to implement name, description, and process.
| Property | Default | Notes |
|---|---|---|
id | slugify(name) | Used as the storage namespace and classifier routing key. |
saveChat | true | Set to false to opt the agent out of chat persistence. |
maxToolRounds | 1 | Override to allow a tool-use loop. A tool-bearing agent that leaves this at 1 will never iterate past the first model call. |
AgentInput
Section titled “AgentInput”public enum AgentInput: Sendable { case text(String)}A convenience property surfaces the string without a pattern match:
let text = input.text // "" when the case is not .textAgentContext
Section titled “AgentContext”Carries per-turn identity, arbitrary params, and the live trace span:
public struct AgentContext: Sendable { public let userId: String public let sessionId: String public let params: [String: JSONValue] public let span: (any SpanHandle)?
public init( userId: String, sessionId: String, params: [String: JSONValue] = [:], span: (any SpanHandle)? = nil )}Child spans attached to context.span appear nested under the orchestrator’s session span in your tracer. See Tracing for details.
AgentEvent
Section titled “AgentEvent”The stream emitted by process yields these cases:
public enum AgentEvent: Sendable { case thinking(String) case textDelta(String) case toolCall(id: String, name: String, arguments: JSONValue) case widget(UIPayload) case final(ConversationMessage) case error(String)}| Case | Notes |
|---|---|
.textDelta | Incremental text chunk; concatenate to build the full response. |
.toolCall | Announced for observability; the tool result is recorded on the trace span, not re-emitted as an event. |
.widget | A structured UI payload from a tool (see UI). Only emitted when the agent’s UIPolicy is .forward. |
.final | The completed ConversationMessage; the orchestrator persists this when saveChat is true. |
.error | A user-facing message (e.g. “network unavailable”). Real failures are thrown through the stream. |
UIPolicy
Section titled “UIPolicy”Both built-in agents accept a UIPolicy parameter that controls whether tool-advertised UI payloads reach the caller:
public enum UIPolicy: Sendable { case forward // emit .widget events (default) case suppress // fold tool data into text; no .widget emitted}See UI for how UIPayload is declared and consumed.
Built-in agents
Section titled “Built-in agents”| Type | Description |
|---|---|
Agent | General-purpose: one LLMClient driving a tool-use loop. |
GroundedAgent | Two-LLM anti-hallucination pattern: a Brain gathers tool output; an isolated Presenter speaks only from that output. |
Both implement AgentProtocol and are interchangeable at the Orchestrator call site.
Rolling your own
Section titled “Rolling your own”Conform to AgentProtocol and implement process. See Custom Agents for a complete example and guidance on overriding maxToolRounds.
Related pages
Section titled “Related pages”- Orchestrator — how agents are routed and chat is persisted.
- Tools — building a
ToolProviderand definingAgentTools. - LLM clients — the
LLMClientprotocol and available implementations. - Messages & events —
ConversationMessage,ContentPart, and the full event model. - Tracing — attaching spans to
AgentContext. - UI —
UIPayloadand how.widgetevents are consumed. - Voice — the voice path that sits outside
AgentProtocol.