Files
YiemAgent/docs/agent_loop_diagram.md
T
2026-07-28 10:31:11 +07:00

326 lines
12 KiB
Markdown

# 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<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
```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<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
```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 |