12 KiB
12 KiB
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
flowchart TD
Start[Start Agent Loop] --> EmitStart[Emitter: AgentStartEvent]
EmitStart --> EmitTurnStart[Emitter: TurnStartEvent]
subgraph SteeringQueue "Check Steering Queue"
SteeringQueue --> HasSteering{Has pending<br/>steering messages?}
HasSteering -- Yes --> ProcessSteering[Process steering messages<br/>Emit MessageStart/End]
ProcessSteering --> AddToContext[Add to context messages]
AddToContext --> PollSteering
HasSteering -- No --> PollSteering
end
PollSteering{Next iteration:<br/>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<br/>with error message]
CheckStop -- normal --> ExtractTools[Extract ToolCall objects]
FailTools --> HasMoreTools
ExtractTools --> HasMoreTools{Has tool<br/>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<br/>& collect results]
SeqExec --> SeqFinalize[Finalize tool call<br/> Emit: ToolExecutionEndEvent]
ParaExec --> ParaFinalize[Finalize tool calls<br/>Emit: ToolExecutionEndEvent]
SeqFinalize --> SeqResults[Create ToolResultMessages]
ParaFinalize --> ParaResults[Create ToolResultMessages]
SeqResults --> AddToolResults
ParaResults --> AddToolResults[Add tool results to context<br/>Emit: MessageStart/End]
end
AddToolResults --> HasMoreTools
CheckFollowUp{Has pending<br/>follow-up messages?}
CheckFollowUp -- Yes --> ProcessFollowUp[Process follow-up messages<br/>Emit MessageStart/End]
ProcessFollowUp --> AddToContext2[Add to context messages]
AddToContext2 --> CheckPrepareNext
CheckPrepareNext{Should prepare<br/>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
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
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<br/>assistant turn]
SQueue --> SProcess[Processed in<br/>next turn iteration]
end
subgraph "Follow-up Queue"
follow[followUp(message)] --> FQueue[Queued to run<br/>when agent would stop]
FQueue --> FProcess[Processed only<br/>when no more work]
end
F --> SProcess
F --> FProcess
Tool Execution Modes
Sequential Execution
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
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
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
struct AgentContext
system_prompt::String
messages::Vector{AgentMessage}
tools::Union{Vector{AgentTool}, Nothing}
end
AgentLoopConfig
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
-
prompt(agent, input)- Start new conversation- Validates no active run
- Normalizes input to messages
- Calls
runPromptMessages
-
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:
- LLM response has stop_reason = "error" or "aborted"
- No tool calls and no pending steering/follow-up messages
should_stop_after_turnreturns true- 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 |