Skip to content

Agent Loop (Executor)

The Executor is the reasoning engine that powers every Moxxy agent. It drives an LLM-based tool-calling loop that processes requests and invokes primitives to accomplish tasks.

Overview

+-----------------------------------------------------------+
|                     Trigger Input                          |
|            (API request, channel message, heartbeat)       |
+-----------------------------+-----------------------------+
                              |
                              v
+-----------------------------------------------------------+
|                       Executor                             |
+-----------------------------------------------------------+
|                                                            |
|   +-----------+    +-------------+    +--------------+    |
|   |   Build   |--->|   LLM Call  |--->|  Parse Tool  |    |
|   |  Context  |    |  (Provider) |    |    Calls     |    |
|   +-----------+    +-------------+    +--------------+    |
|        ^                                     |            |
|        |           +-------------+           |            |
|        +-----------| Invoke      |<----------+            |
|                    | Primitives  |                        |
|                    +-------------+                        |
|                          |                                |
|                          v                                |
|                    +-------------+                        |
|                    | Emit Events |                        |
|                    | (60 types)  |                        |
|                    +-------------+                        |
|                                                            |
+-----------------------------+-----------------------------+
                              |
                              v
+-----------------------------------------------------------+
|                     Response Output                        |
|              (To user, channel, parent agent)              |
+-----------------------------------------------------------+

The Execution Loop

The Executor runs a loop for each agent run:

  1. Build context - Assemble the system prompt (persona + primitive catalog + skill catalog) and conversation history (STM + relevant LTM)
  2. Call LLM - Send the full message history to the configured provider
  3. Parse tool calls - Extract tool_use/function calling responses from the LLM output
  4. Invoke primitives - Execute each requested primitive, collecting results
  5. Emit events - Publish SSE events for the step (tool invocations, results, errors)
  6. Loop or respond - If tool calls were made, append results to history and go to step 2; if the LLM returned only text, deliver the response and end

Multiple Tool Calls Per Turn

The LLM can request multiple tool calls in a single response. The Executor processes all of them before returning results to the LLM for the next turn. This enables parallel operations within a single iteration.

No Fixed Iteration Limit

The loop continues as long as the LLM makes tool calls. Stuck detection and automatic recovery handle cases where the agent gets into an unproductive loop.

Tool Calling Format

Moxxy uses standard LLM tool_use/function calling -- the native mechanism provided by each provider. There is no custom XML invoke format.

How It Works

  1. The system prompt includes the full catalog of available primitives as tool definitions
  2. The LLM responds with structured tool call requests (format varies by provider)
  3. The Executor dispatches each tool call to the corresponding primitive handler
  4. Results are returned to the LLM as tool result messages

Provider Abstraction

The Executor supports multiple LLM providers through a unified abstraction:

ProviderProtocol
AnthropicMessages API with tool_use
OpenAIChat Completions with function calling
OllamaOpenAI-compatible API
OpenAI-compatibleAny endpoint following the OpenAI chat format

The provider is configured per agent. The Executor translates between the internal primitive format and each provider's tool calling convention.

Stuck Detection and Recovery

The Executor monitors the agent loop for unproductive patterns:

  • Repeated identical tool calls
  • Tool calls that consistently fail
  • Excessive iterations without progress

When stuck behavior is detected, the Executor intervenes with recovery strategies to break the loop and guide the agent toward completion or graceful termination.

Event System

Every step in the execution loop emits SSE (Server-Sent Events) for full observability. Moxxy defines 60 event types.

EventEnvelope

Each event is wrapped in an EventEnvelope:

json
{
  "event_id": "evt_abc123",
  "timestamp": "2026-03-30T10:15:00Z",
  "agent_id": "agt_xyz789",
  "run_id": "run_def456",
  "sequence": 42,
  "type": "tool_call_start",
  "data": { ... }
}

Subscribing to Events

bash
curl -N http://127.0.0.1:3000/v1/agents/researcher/events \
  -H "Authorization: Bearer mox_your_token" \
  -H "Accept: text/event-stream"

Key Event Types

EventDescription
run_startA new run has begun
run_endA run has completed
llm_requestLLM call initiated
llm_responseLLM response received
tool_call_startPrimitive invocation starting
tool_call_endPrimitive invocation complete
tool_call_errorPrimitive invocation failed
tokenStreaming text token
agent_spawnSub-agent spawned
stuck_detectedStuck behavior detected
recovery_actionRecovery strategy applied

Events are persisted to the database by a background task for full audit logging.

Context Building

System Prompt

The system prompt is assembled from:

  1. Persona - The agent's configured personality and instructions
  2. Primitive catalog - All available primitives (after allowlist filtering) formatted as tool definitions
  3. Skill catalog - Approved skills with their instructions and allowed primitives

Memory Injection

Before each LLM call:

  1. STM - Current session context (recent messages and working state)
  2. LTM retrieval - Semantic search for memories relevant to the current prompt
  3. Journal context - Recent journal entries if applicable

Sub-Agent Spawning

When an agent invokes agent.spawn, the Executor:

  1. Creates a new agent with the specified configuration
  2. Sets the parent relationship and increments depth
  3. Checks depth and fan-out limits
  4. Starts a new Executor loop for the sub-agent
  5. Returns results to the parent agent when complete

Limits

  • Depth limit - Maximum nesting depth for sub-agent chains
  • Fan-out limit - Maximum concurrent sub-agents per parent

These limits prevent runaway agent hierarchies.

Error Handling

Primitive Errors

When a primitive fails, the error is returned to the LLM as a tool result with error information. The agent can then decide to:

  1. Try a different approach
  2. Ask the user for help
  3. Report the error

LLM Errors

If the LLM call fails (rate limit, network error, etc.):

  1. The error is logged and emitted as an event
  2. Retries are attempted with backoff
  3. If retries are exhausted, the run transitions to the error status

Secret Redaction

The vault system ensures that secrets are redacted from SSE events. Even if an agent reads a secret via vault.get, the secret value is scrubbed from events before they are emitted or persisted.

Configuration

Per-Agent Settings

Agent configuration is stored in config.yaml:

yaml
name: researcher
provider: anthropic
model: claude-sonnet-4-20250514
persona: |
  You are a research assistant specialized in
  finding and summarizing technical information.

Provider Configuration

Provider API keys are stored in the vault or set via environment variables:

VariableProvider
ANTHROPIC_API_KEYAnthropic (Claude)
OPENAI_API_KEYOpenAI

Ollama runs locally and does not require an API key.

Open source · Self-hosted · Data sovereign