Portal Community

The Agentic Loop

public class AgentReasoningLoop
{
    private const int MaxIterations = 10;

    public async Task<AgentResponse> RunAsync(
        AgentComposite agent,
        IReadOnlyList<LLMMessage> messages,
        CancellationToken ct)
    {
        var workingMessages = messages.ToList();
        int iterations = 0;

        while (iterations < MaxIterations)
        {
            iterations++;

            // Call the LLM with current messages + tool schemas
            var response = await _llmProvider.CompleteAsync(
                workingMessages,
                agent.ToolRegistry.GetSchemas(),
                agent.LLMConfig,
                ct);

            // No tool calls — LLM produced a final text response
            if (!response.ToolCalls.Any())
                return new AgentResponse { Content = response.Content };

            // Append the LLM's assistant message (with tool_calls) to history
            workingMessages.Add(new LLMMessage(Role.Assistant, response));

            // Execute each tool call and append results
            foreach (var toolCall in response.ToolCalls)
            {
                string result = await agent.ToolRegistry.ExecuteAsync(
                    toolCall, _conversationContext, ct);

                workingMessages.Add(new LLMMessage(Role.Tool)
                {
                    ToolCallId = toolCall.Id,
                    Content    = result
                });
            }
            // Loop continues — LLM sees tool results and produces next response
        }

        // Max iterations reached — return partial response
        return new AgentResponse
        {
            Content = "I was unable to complete this task within the allowed steps.",
            IsIncomplete = true
        };
    }
}

Parallel Tool Calls

Modern LLMs (Claude 3+, GPT-4o) can request multiple tool calls simultaneously in a single response. Octopus executes them in parallel:

// LLM response with parallel tool calls:
{
  "role":       "assistant",
  "tool_calls": [
    { "id": "tc_01", "name": "get_leave_balance",  "input": { "employeeId": "EMP-1042" } },
    { "id": "tc_02", "name": "get_manager_details", "input": { "employeeId": "EMP-1042" } }
  ]
}

// Octopus executes both in parallel:
var tasks = response.ToolCalls.Select(tc =>
    agent.ToolRegistry.ExecuteAsync(tc, context, ct));

var results = await Task.WhenAll(tasks);

// Append all results (maintaining tool_call_id pairing)
foreach (var (call, result) in response.ToolCalls.Zip(results))
{
    workingMessages.Add(new LLMMessage(Role.Tool)
    {
        ToolCallId = call.Id,
        Content    = result
    });
}

Loop Control Settings

SettingDefaultPurpose
MaxToolIterations10Maximum tool call rounds before the loop exits with an incomplete response
ToolTimeoutSeconds30Timeout per individual tool execution
ParallelToolExecutiontrueExecute parallel tool calls concurrently; disable if tools have side effects that must be sequential

Loop Termination Conditions

ConditionBehaviour
LLM returns text with no tool callsNormal exit — text response returned to user
Max iterations reachedPartial exit — incomplete response message returned
Tool execution errorError result injected into context; LLM decides how to proceed
LLM requests end_turn stop reasonNormal exit — provider-specific stop signal
CancellationToken cancelledImmediate exit; partial response or error returned
Max Iterations Prevents Infinite Loops

A poorly written tool or an LLM that keeps calling the same tool can create an infinite loop. The MaxToolIterations limit is a hard safety cap. Set it to the maximum number of steps your most complex task requires — typically 5–10. If you observe agents hitting this limit, investigate whether the tool descriptions are clear enough for the LLM to know when it has enough information.