Files
YiemAgent/docs/agent_loop_sequence_diagram.md
T
2026-07-28 09:39:21 +07:00

12 KiB

Agent Loop Sequence Diagram

sequenceDiagram
    participant User
    participant Agent
    participant AgentLoop
    participant LLM
    participant Tool
    participant Session

    User->>Agent: prompt(messages)
    activate Agent
    Agent->>Agent: normalizePromptInput(messages)
    Agent->>Agent: runPromptMessages(messages)
    
    alt Has queued steering messages
        Agent->>Agent: drain(steering_queue)
        Agent->>Agent: runPromptMessages(queued_steering)
    else Has follow-up messages
        Agent->>Agent: drain(follow_up_queue)
        Agent->>Agent: runPromptMessages(queued_follow_ups)
    else Normal prompt
        Agent->>Agent: createLoopConfig(options)
        Agent->>Agent: createActiveRun()
        
        Note over Agent,Session: Start AgentLoop
        Agent->>AgentLoop: agentLoop(messages, context, config, signal, stream_fn)
        activate AgentLoop
        
        Note over AgentLoop: Emit AgentStartEvent
        AgentLoop->>AgentLoop: emit(AgentStartEvent())
        
        loop For each prompt message
            AgentLoop->>AgentLoop: emit(MessageStartEvent(msg))
            AgentLoop->>AgentLoop: emit(MessageEndEvent(msg))
        end
        
        loop Main Agent Loop
            Note over AgentLoop: 1. Handle queued messages
            
            alt Steering/Follow-up messages exist
                AgentLoop->>AgentLoop: drain(steering_queue)
                loop For each pending message
                    AgentLoop->>AgentLoop: emit(MessageStartEvent(msg))
                    AgentLoop->>AgentLoop: push to context.messages
                    AgentLoop->>AgentLoop: emit(MessageEndEvent(msg))
                end
            end
            
            Note over AgentLoop: 2. Get LLM Response
            
            AgentLoop->>AgentLoop: transform_context?(messages)
            AgentLoop->>AgentLoop: convert_to_llm(messages)
            AgentLoop->>LLM: stream_function(model, context, config)
            activate LLM
            LLM-->>AgentLoop: response stream
            
            alt Streaming enabled
                loop For each event in stream
                    AgentLoop->>AgentLoop: emit(MessageStartEvent)
                    AgentLoop->>AgentLoop: emit(MessageUpdateEvent)
                end
                LLM-->>AgentLoop: final message
            else Non-streaming
                LLM-->>AgentLoop: complete response
            end
            
            deactivate LLM
            AgentLoop->>AgentLoop: emit(MessageEndEvent(final_message))
            AgentLoop->>AgentLoop: push message to context.messages
            
            Note over AgentLoop: 3. Check for Tool Calls
            
            AgentLoop->>AgentLoop: extract_tool_calls(message.content)
            
            alt Tool calls exist
                Note over AgentLoop: 4. Execute Tools
                
                alt Tool execution mode == SEQUENTIAL
                    or has_sequential_tool_call
                    AgentLoop->>AgentLoop: executeToolCallsSequential(...)
                    activate AgentLoop
                    
                    loop For each tool call
                        AgentLoop->>AgentLoop: emit(ToolExecutionStartEvent)
                        
                        alt Tool not found
                            AgentLoop->>AgentLoop: createErrorToolResult
                            AgentLoop->>AgentLoop: emit(ToolExecutionEndEvent)
                        else Tool found
                            alt before_tool_call hook exists
                                AgentLoop->>AgentLoop: before_tool_call(context, signal)
                                alt Hook blocks execution
                                    AgentLoop->>AgentLoop: createErrorToolResult
                                    AgentLoop->>AgentLoop: emit(ToolExecutionEndEvent)
                                else Execution allowed
                                    AgentLoop->>Agent: prepareToolCall(tool, tool_call, args)
                                    activate Agent
                                    
                                    alt Has prepare_arguments
                                        Agent->>Agent: prepare_arguments(args)
                                        deactivate Agent
                                        activate AgentLoop
                                        AgentLoop->>AgentLoop: validateToolArguments
                                    end
                                    
                                    alt Has before_tool_call hook
                                        AgentLoop->>Agent: before_tool_call(context, signal)
                                        alt Hook blocks
                                            AgentLoop->>AgentLoop: createErrorToolResult
                                        else
                                            AgentLoop->>Tool: execute(tool_call_id, args, signal, on_update)
                                            activate Tool
                                            Tool-->>AgentLoop: result
                                            deactivate Tool
                                        end
                                    else No hooks
                                        AgentLoop->>Tool: execute(tool_call_id, args, signal, on_update)
                                        activate Tool
                                        Tool-->>AgentLoop: result
                                        deactivate Tool
                                    end
                                    
                                    alt Has after_tool_call hook
                                        AgentLoop->>Agent: after_tool_call(context, signal)
                                        alt Hook modifies result
                                            AgentLoop->>AgentLoop: apply after_result
                                        end
                                    end
                                    
                                    AgentLoop->>AgentLoop: emit(ToolExecutionEndEvent)
                                    AgentLoop->>AgentLoop: createToolResultMessage
                                    AgentLoop->>AgentLoop: emit(MessageStartEvent)
                                    AgentLoop->>AgentLoop: emit(MessageEndEvent)
                                    AgentLoop->>AgentLoop: push to context.messages
                                end
                            end
                            deactivate Agent
                            activate AgentLoop
                        end
                        
                        alt Signal aborted
                            break
                        end
                    end
                    
                    AgentLoop->>AgentLoop: return ExecutedToolCallBatch
                    deactivate AgentLoop
                    activate AgentLoop
                else Tool execution mode == PARALLEL
                    AgentLoop->>AgentLoop: executeToolCallsParallel(...)
                    activate AgentLoop
                    
                    loop For each tool call
                        alt Tool not found or immediate
                            AgentLoop->>AgentLoop: execute synchronously
                        else Needs execution
                            AgentLoop->>AgentLoop: spawn async task
                        end
                    end
                    
                    AgentLoop->>AgentLoop: wait for all tasks
                    AgentLoop->>AgentLoop: collect results
                    AgentLoop->>AgentLoop: return ExecutedToolCallBatch
                    deactivate AgentLoop
                    activate AgentLoop
                end
                
                Note over AgentLoop: 5. Emit Turn End Event
                AgentLoop->>AgentLoop: emit(TurnEndEvent(message, tool_results))
                
                Note over AgentLoop: 6. Prepare Next Turn
                
                alt Has prepare_next_turn hook
                    AgentLoop->>Agent: prepare_next_turn(context)
                    activate Agent
                    alt Returns AgentLoopTurnUpdate
                        Agent->>Agent: update context
                        Agent->>Agent: update model
                        Agent->>Agent: update thinking_level
                        Agent->>AgentLoop: return updated config
                        deactivate Agent
                        activate AgentLoop
                    end
                end
                
                Note over AgentLoop: 7. Check Stop Condition
                
                alt should_stop_after_turn returns true
                    AgentLoop->>AgentLoop: emit(AgentEndEvent)
                    break
                end
                
                Note over AgentLoop: 8. Get Next Steering Messages
                AgentLoop->>AgentLoop: drain(steering_queue)
                
            else No tool calls
                Note over AgentLoop: 5. Emit Turn End Event
                AgentLoop->>AgentLoop: emit(TurnEndEvent(message, []))
                
                Note over AgentLoop: 6. Prepare Next Turn
                alt Has prepare_next_turn hook
                    AgentLoop->>Agent: prepare_next_turn(context)
                    activate Agent
                    alt Returns AgentLoopTurnUpdate
                        Agent->>Agent: update context
                        Agent->>Agent: update model
                        Agent->>Agent: update thinking_level
                        Agent->>AgentLoop: return updated config
                        deactivate Agent
                        activate AgentLoop
                    end
                end
                
                Note over AgentLoop: 7. Check Stop Condition
                alt should_stop_after_turn returns true
                    AgentLoop->>AgentLoop: emit(AgentEndEvent)
                    break
                end
                
                Note over AgentLoop: 8. Get Next Steering Messages
                AgentLoop->>AgentLoop: drain(steering_queue)
            end
            
            alt Pending messages exist
                continue loop
            end
            
            Note over AgentLoop: 9. Get Follow-up Messages
            AgentLoop->>AgentLoop: drain(follow_up_queue)
            
            alt Follow-up messages exist
                AgentLoop->>AgentLoop: set pending_messages = follow_up
                continue loop
            end
            
            break
        end
        
        Note over AgentLoop: 10. Final Agent End Event
        AgentLoop->>AgentLoop: emit(AgentEndEvent(messages))
        deactivate AgentLoop
        deactivate Agent
        
        Note over Agent,Session: Persist to Session
        Agent->>Session: appendMessage(message)
        
        User<<--Agent: messages (via promise)
    end
    
    Note over Agent: Resume normal operation
    deactivate Agent

Key Components

Agent Layer

  • Agent: High-level wrapper managing state, queuing, and events
  • steering_queue: Messages injected after assistant turn completes
  • follow_up_queue: Messages that run only when agent would otherwise stop

AgentLoop Layer

  • agentLoop: Entry point for new prompts
  • agentLoopContinue: Continue from existing transcript
  • runLoop: Main execution loop handling:
    1. Pending message handling
    2. LLM calls with streaming
    3. Tool execution (sequential/parallel)
    4. Turn lifecycle events
    5. Next turn preparation
    6. Stop condition checking

Lifecycle Events

  • AgentStartEvent / AgentEndEvent - Agent lifecycle boundaries
  • TurnStartEvent / TurnEndEvent - Conversation turns
  • MessageStartEvent / MessageEndEvent - Message processing
  • ToolExecutionStartEvent / ToolExecutionEndEvent - Tool execution

Data Flow

  1. Input: User messages → Agent normalization → AgentLoop
  2. LLM Call: Messages transformed → LLM stream → Assistant message
  3. Tool Execution: Tool calls extracted → Prepared → Executed → Results
  4. State Update: New messages appended to context
  5. Output: AgentEndEvent with final messages

Queue Processing Order

  1. Initial steering messages (if any)
  2. Main loop:
    • Steering/follow-up messages (if any)
    • LLM call
    • Tool execution (if any)
    • Turn end event
    • Next turn preparation
    • Check stop condition
  3. Follow-up messages (after loop exits)