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:
- Build context - Assemble the system prompt (persona + primitive catalog + skill catalog) and conversation history (STM + relevant LTM)
- Call LLM - Send the full message history to the configured provider
- Parse tool calls - Extract tool_use/function calling responses from the LLM output
- Invoke primitives - Execute each requested primitive, collecting results
- Emit events - Publish SSE events for the step (tool invocations, results, errors)
- 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
- The system prompt includes the full catalog of available primitives as tool definitions
- The LLM responds with structured tool call requests (format varies by provider)
- The Executor dispatches each tool call to the corresponding primitive handler
- Results are returned to the LLM as tool result messages
Provider Abstraction
The Executor supports multiple LLM providers through a unified abstraction:
| Provider | Protocol |
|---|---|
| Anthropic | Messages API with tool_use |
| OpenAI | Chat Completions with function calling |
| Ollama | OpenAI-compatible API |
| OpenAI-compatible | Any 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:
{
"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
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
| Event | Description |
|---|---|
run_start | A new run has begun |
run_end | A run has completed |
llm_request | LLM call initiated |
llm_response | LLM response received |
tool_call_start | Primitive invocation starting |
tool_call_end | Primitive invocation complete |
tool_call_error | Primitive invocation failed |
token | Streaming text token |
agent_spawn | Sub-agent spawned |
stuck_detected | Stuck behavior detected |
recovery_action | Recovery 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:
- Persona - The agent's configured personality and instructions
- Primitive catalog - All available primitives (after allowlist filtering) formatted as tool definitions
- Skill catalog - Approved skills with their instructions and allowed primitives
Memory Injection
Before each LLM call:
- STM - Current session context (recent messages and working state)
- LTM retrieval - Semantic search for memories relevant to the current prompt
- Journal context - Recent journal entries if applicable
Sub-Agent Spawning
When an agent invokes agent.spawn, the Executor:
- Creates a new agent with the specified configuration
- Sets the parent relationship and increments depth
- Checks depth and fan-out limits
- Starts a new Executor loop for the sub-agent
- 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:
- Try a different approach
- Ask the user for help
- Report the error
LLM Errors
If the LLM call fails (rate limit, network error, etc.):
- The error is logged and emitted as an event
- Retries are attempted with backoff
- If retries are exhausted, the run transitions to the
errorstatus
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:
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:
| Variable | Provider |
|---|---|
ANTHROPIC_API_KEY | Anthropic (Claude) |
OPENAI_API_KEY | OpenAI |
Ollama runs locally and does not require an API key.