# Agent Loop Diagram ## Overview This document describes the agent loop execution flow in AgentCore.jl, showing how the agent processes messages, executes tools, and handles steering/follow-up messages. ## Architecture Layers ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Agent (High-Level) │ │ - State management, event streaming, queueing │ │ - Steering queue (steer()) and Follow-up queue (followUp()) │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ AgentLoop (Low-Level) │ │ - Core loop execution with tool calling │ │ - Tool execution (parallel/sequential) │ │ - Event emission lifecycle │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ LLM Provider │ │ - Stream function calls the LLM API │ │ - Returns assistant response (text + tool calls) │ └─────────────────────────────────────────────────────────────────────────┘ ``` ## Main Agent Loop Flow ```mermaid flowchart TD Start[Start Agent Loop] --> EmitStart[Emitter: AgentStartEvent] EmitStart --> EmitTurnStart[Emitter: TurnStartEvent] subgraph SteeringQueue "Check Steering Queue" SteeringQueue --> HasSteering{Has pending
steering messages?} HasSteering -- Yes --> ProcessSteering[Process steering messages
Emit MessageStart/End] ProcessSteering --> AddToContext[Add to context messages] AddToContext --> PollSteering HasSteering -- No --> PollSteering end PollSteering{Next iteration:
Poll steering queue again} subgraph AssistantLoop "Assistant Response Loop" PollSteering --> StreamResponse[Stream Assistant Response from LLM] StreamResponse --> CheckStop{Stop reason?} CheckStop -- error/aborted --> EmitTurnEnd1[Emit: TurnEndEvent] EmitTurnEnd1 --> EmitAgentEnd1[Emit: AgentEndEvent] EmitAgentEnd1 --> End1[End Loop] CheckStop -- length/truncated --> FailTools[Fail all tool calls
with error message] CheckStop -- normal --> ExtractTools[Extract ToolCall objects] FailTools --> HasMoreTools ExtractTools --> HasMoreTools{Has tool
calls?} HasMoreTools -- Yes --> ExecuteTools[Execute Tools] HasMoreTools -- No --> CheckFollowUp subgraph ToolExecution "Tool Execution" direction TB ExecuteTools --> ExecMode{Execution Mode?} ExecMode -- Sequential --> SeqLoop[For each tool call:] ExecMode -- Parallel --> ParaLoop[For each tool call:] SeqLoop --> SeqPrepare[Prepare tool call] ParaLoop --> ParaPrepare[Prepare tool call] SeqPrepare --> SeqExec[Execute sequentially] ParaPrepare --> ParaExec[Execute in parallel
& collect results] SeqExec --> SeqFinalize[Finalize tool call
Emit: ToolExecutionEndEvent] ParaExec --> ParaFinalize[Finalize tool calls
Emit: ToolExecutionEndEvent] SeqFinalize --> SeqResults[Create ToolResultMessages] ParaFinalize --> ParaResults[Create ToolResultMessages] SeqResults --> AddToolResults ParaResults --> AddToolResults[Add tool results to context
Emit: MessageStart/End] end AddToolResults --> HasMoreTools CheckFollowUp{Has pending
follow-up messages?} CheckFollowUp -- Yes --> ProcessFollowUp[Process follow-up messages
Emit MessageStart/End] ProcessFollowUp --> AddToContext2[Add to context messages] AddToContext2 --> CheckPrepareNext CheckPrepareNext{Should prepare
next turn?} CheckPrepareNext -- Yes --> PrepareNext[Call prepare_next_turn hook] PrepareNext --> UpdateContext[Update context & config] UpdateContext --> CheckStop2 CheckPrepareNext -- No --> CheckStop2 CheckStop2{Stop after turn?} CheckStop2 -- Yes --> EmitAgentEnd2[Emit: AgentEndEvent] EmitAgentEnd2 --> End2[End Loop] CheckStop2 -- No --> PollSteering end CheckFollowUp -- No --> CheckPrepareNext ``` ## Detailed Event Lifecycle ```mermaid sequenceDiagram participant Agent participant Loop as AgentLoop participant Emitter as Event Sink participant LLM participant Tools Agent->>Loop: agentLoop(prompts, context, config) Loop->>Emitter: AgentStartEvent Loop->>Emitter: TurnStartEvent note over Loop: Process prompts Loop->>Emitter: MessageStartEvent(prompt) Loop->>Emitter: MessageEndEvent(prompt) Loop->>LLM: stream_function(model, context, config) LLM-->>Loop: AssistantResponse with ToolCalls note over Loop: Process response Loop->>Emitter: MessageStartEvent(assistant) alt Has Tool Calls Loop->>Tools: Execute tools Tools-->>Loop: ToolResults note over Loop: Emit tool execution events Loop->>Emitter: ToolExecutionStartEvent Loop->>Emitter: ToolExecutionUpdateEvent (if streaming) Loop->>Emitter: ToolExecutionEndEvent Loop->>Emitter: MessageStartEvent(toolResult) Loop->>Emitter: MessageEndEvent(toolResult) end Loop->>Emitter: MessageEndEvent(assistant) Loop->>Emitter: TurnEndEvent(message, tool_results) note over Loop: Check for follow-up/steering alt Has more work Loop->>Loop: Continue loop else Done Loop->>Emitter: AgentEndEvent(messages) end ``` ## Steering vs Follow-up Messages ```mermaid graph LR subgraph "Main Conversation" A[User Message] --> B[Assistant Response] B --> C[Tool Execution] C --> D[Tool Result] D --> E{Decision Point} end E -->|Continue loop| B E -->|Stop & check queues| F subgraph "Steering Queue" steer[steer(message)] --> SQueue[Queued after
assistant turn] SQueue --> SProcess[Processed in
next turn iteration] end subgraph "Follow-up Queue" follow[followUp(message)] --> FQueue[Queued to run
when agent would stop] FQueue --> FProcess[Processed only
when no more work] end F --> SProcess F --> FProcess ``` ## Tool Execution Modes ### Sequential Execution ```mermaid flowchart LR Tool1[Tool Call 1] --> Prepare1[Prepare] Prepare1 --> Execute1[Execute] Execute1 --> Finalize1[Finalize] Finalize1 --> Result1[Tool Result 1] Result1 --> Prepare2[Prepare] Prepare2 --> Execute2[Execute] Execute2 --> Finalize2[Finalize] Finalize2 --> Result2[Tool Result 2] note right of Execute1 "Must complete before next tool" note right of Execute2 "Tools run one at a time" ``` ### Parallel Execution ```mermaid flowchart LR Tool1[Tool Call 1] --> Prepare1[Prepare] Tool2[Tool Call 2] --> Prepare2[Prepare] Tool3[Tool Call 3] --> Prepare3[Prepare] Prepare1 --> Execute1[Execute] Prepare2 --> Execute2[Execute] Prepare3 --> Execute3[Execute] Execute1 --> Finalize1[Finalize] --> Result1[Tool Result 1] Execute2 --> Finalize2[Finalize] --> Result2[Tool Result 2] Execute3 --> Finalize3[Finalize] --> Result3[Tool Result 3] note right of Prepare1 "Preparation can happen" note right of Execute1 "All tools execute" note right of Finalize1 "Results collected in order" ``` ## Message Conversion at LLM Boundary ```mermaid flowchart LR subgraph "Agent Messages (Internal)" AM1[UserMessage] AM2[AssistantMessage] AM3[ToolResultMessage] AM4[CompactionSummaryMessage] AM5[BranchSummaryMessage] AM6[CustomMessage] end AM1 --> Convert[convertToLlm] AM2 --> Convert AM3 --> Convert AM4 --> Convert AM5 --> Convert AM6 --> Convert Convert --> LM1[UserMessage] Convert --> LM2[AssistantMessage] Convert --> LM3[ToolResultMessage] Convert --> LM4[UserMessage (summary)] Convert --> LM5[UserMessage (branch)] Convert --> LM6[UserMessage (custom)] LM1 --> LLM[LLM API] LM2 --> LLM LM3 --> LLM LM4 --> LLM LM5 --> LLM LM6 --> LLM ``` ## Key Data Structures ### AgentContext ```julia struct AgentContext system_prompt::String messages::Vector{AgentMessage} tools::Union{Vector{AgentTool}, Nothing} end ``` ### AgentLoopConfig ```julia struct AgentLoopConfig model::Model reasoning::Union{ThinkingLevel, Nothing} session_id::Union{String, Nothing} convert_to_llm::Function transform_context::Union{Function, Nothing} get_api_key::Union{Function, Nothing} get_steering_messages::Function get_follow_up_messages::Function before_tool_call::Union{Function, Nothing} after_tool_call::Union{Function, Nothing} prepare_next_turn::Union{Function, Nothing} should_stop_after_turn::Function tool_execution::ToolExecutionMode # ... other options end ``` ## Entry Points 1. **`prompt(agent, input)`** - Start new conversation - Validates no active run - Normalizes input to messages - Calls `runPromptMessages` 2. **`continue!(agent)`** - Continue from last message - Checks last message is not assistant - Drains steering/follow-up queues first - Calls agent loop continuation ## Termination Conditions The agent loop terminates when: 1. LLM response has stop_reason = "error" or "aborted" 2. No tool calls and no pending steering/follow-up messages 3. `should_stop_after_turn` returns true 4. Agent is aborted via abort signal ## Event Summary | Event | When Emitted | |-------|--------------| | `AgentStartEvent` | Loop begins | | `TurnStartEvent` | Each conversation turn | | `MessageStartEvent` | Message added to context | | `MessageEndEvent` | Message fully processed | | `MessageUpdateEvent` | Streaming updates | | `ToolExecutionStartEvent` | Tool execution begins | | `ToolExecutionUpdateEvent` | Tool execution progress | | `ToolExecutionEndEvent` | Tool execution completes | | `TurnEndEvent` | Turn completes | | `AgentEndEvent` | Loop terminates |