State & observation
This page describes control flow (iterate until no tool calls,maxAgenticIterations). ForStatefulLLM,QueuedLLM, and the three state models (LLMRuntimeState,LLMRequestState,AgenticLoopState) — includingLLMProtocolAdapter.agenticLoopUpdates— see LLM state and observation.
The LLMProtocolAdapter implements an agentic loop pattern that allows the LLM to use tools and synthesize their results into a final answer. This pattern is essential for creating agents that can reason about tool outputs and provide coherent responses based on the tool execution results.
Tool execution timeouts: each executeTool call is bounded by Configuration.toolCallTimeout (default 5 minutes). SwiftAgentKitOrchestrator uses OrchestratorConfig.toolCallTimeout for MCP, A2A, and ToolManager dispatch. See SwiftAgentKitOrchestrator — Tool dispatch order and withToolCallTimeout in the core module.
Previously, when the adapter received a message through handleTaskSendWithTools, the flow was:
- Send the conversation (the message and task history) to the LLM
- Append response to message parts
- Call tools (if necessary)
- Append tool call results to message parts
- Update the task artifacts and send a completed status
The issue: The LLM never had a chance to process the tool results and synthesize a final answer. The adapter would just append raw tool outputs to the response and complete the task.
The improved implementation uses an agentic loop that continues until the LLM provides a final answer without requesting additional tool calls:
┌─────────────────────────────────────────────┐
│ 1. Send conversation to LLM (with tools) │
└──────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 2. LLM responds (with/without tool calls) │
└──────────────┬──────────────────────────────┘
│
▼
┌──────┴──────┐
│ Tool calls? │
└──────┬──────┘
│
┌─────────┼─────────┐
│ Yes │ No
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 3. Execute │ │ 5. Return │
│ tools │ │ final │
└──────┬──────┘ │ answer │
│ └─────────────┘
▼
┌─────────────────────────────┐
│ 4. Append tool results to │
│ conversation & go to #1 │
└─────────────────────────────┘
-
Initial LLM Call: The adapter sends the user's message along with the conversation history and available tools to the LLM.
-
Process Response: The LLM returns a response that may contain:
- Text content (thinking, reasoning, or final answer)
- Tool calls (requests to execute specific tools)
-
Execute Tools (if requested): If the LLM requested tool calls:
- Execute each tool with its parameters
- Capture the tool results
- Append tool results to the conversation as "tool" messages
- Loop back to step 1 with the updated conversation
-
Final Answer: When the LLM responds without tool calls, that response is treated as the final answer and sent to the user.
-
Max Iterations: To prevent infinite loops, the adapter has a configurable
maxAgenticIterationsparameter (default: 10).
You can configure the maximum number of agentic iterations when creating the adapter:
let adapter = LLMProtocolAdapter(
llm: myLLM,
model: "gpt-4",
maxAgenticIterations: 15, // Default is 10
// ... other configuration
)Or using the Configuration struct:
let config = LLMProtocolAdapter.Configuration(
model: "gpt-4",
maxAgenticIterations: 15,
// ... other configuration
)
let adapter = LLMProtocolAdapter(llm: myLLM, configuration: config)"What's the weather in San Francisco and what restaurants are nearby?"
- LLM Response: "I need to check the weather first."
- Tool Calls:
get_weather(location: "San Francisco") - Tool Result: "72°F, sunny"
- LLM Response: "Now let me find restaurants."
- Tool Calls:
find_restaurants(location: "San Francisco") - Tool Result: "Found 50 restaurants including..."
- LLM Response: "Based on the weather and restaurant data, here's my recommendation..."
- Tool Calls: (none)
- Result: This becomes the final answer sent to the user
- Coherent Responses: The LLM can synthesize tool results into a natural, conversational answer
- Multi-Step Reasoning: The LLM can use tool results to inform subsequent tool calls
- Context-Aware: The LLM maintains context across multiple tool executions
- User-Friendly: Users receive a polished final answer rather than raw tool outputs
- Executes the full agentic loop before returning
- Only sends the final synthesized answer to the user
- Logs each iteration for debugging
- Streams each LLM response as it's generated
- Shows tool execution status to the user
- Continues streaming through multiple iterations
- Sends a final consolidated response at the end
The adapter logs important events during the agentic loop:
[INFO] Agentic iteration 1/10
[INFO] Executing 2 tool call(s)
[INFO] Agentic iteration 2/10
[INFO] Final response received (no tool calls)
Or in case of hitting the limit:
[WARNING] Max agentic iterations reached without final response
-
Set Reasonable Limits: Default of 10 iterations is usually sufficient. Increase if your use case requires complex multi-step reasoning.
-
Tool Design: Design tools to return concise, relevant information. The LLM needs to process these results, so verbose outputs can impact quality.
-
System Prompts: Consider adding instructions in your system prompt about how to use tools and synthesize results:
var prompt = DynamicPrompt(template: """ When using tools, always synthesize the results into a clear, concise answer for the user. Don't just repeat tool outputs. """) systemPrompt: prompt
-
Monitor Logs: Watch the logs to understand how many iterations your typical requests require and adjust accordingly.
If you're upgrading from the previous version that didn't implement the agentic loop:
-
No Code Changes Required: The new parameter has a default value, so existing code continues to work.
-
Behavioral Change: Your adapter will now provide synthesized answers instead of raw tool outputs. This is generally an improvement but may change the format of responses.
-
Performance: The agentic loop may increase latency for tool-using requests, as it requires multiple LLM calls. Monitor your performance metrics.
This means the LLM kept requesting tool calls without providing a final answer. Consider:
- Increasing
maxAgenticIterations - Reviewing your system prompt to encourage final answers
- Checking if tools are returning useful information
The LLM might be hitting token limits. Consider:
- Increasing
maxTokensin your configuration - Asking tools to return more concise results
- Breaking complex queries into simpler ones
The LLM might be unnecessarily calling tools. Consider:
- Improving your system prompt to encourage efficiency
- Ensuring tools have clear, descriptive descriptions
- Reducing the number of available tools
- LLM state and observation —
StatefulLLM,QueuedLLM,LLMRuntimeState,LLMRequestState,AgenticLoopState,agenticLoopUpdates - LLMProtocolAdapter — adapter API and FIFO / queue discussion