12 KiB
12 KiB
Agent Loop Sequence Diagram (ASCII)
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ ┌───────────┐
│ User │─────>│ Agent │─────>│ AgentLoop │─────>│ LLM │
└─────────────┘ └─────────────┘ └─────────────────┘ └───────────┘
│
▼
┌─────────────┐ ┌─────────────────┐ ┌───────────┐ ┌─────────────┐
│ User │<─────│ AgentLoop │─────>│ Agent │─────>│ Session │
└─────────────┘ └─────────────────┘ └───────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Session │
└─────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ AGENT LOOP MAIN FLOW │
└─────────────────────────────────────────────────────────────────────────────────────────┘
User
│
│ prompt(messages)
▼
Agent
│
│ normalizePromptInput()
│ runPromptMessages()
│
├─ alt steering_queue has messages
│ │ drain(steering_queue)
│ │ runPromptMessages()
│
├─ alt follow_up_queue has messages
│ │ drain(follow_up_queue)
│ │ runPromptMessages()
│
└─ else (normal prompt)
│ createLoopConfig()
│ createActiveRun()
│
▼
AgentLoop
│
│ agentLoop(messages, context, config)
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MAIN LOOP │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Handle queued messages │
│ ├─ drain(steering_queue) │
│ ├─ emit(MessageStartEvent) │
│ ├─ push to context.messages │
│ └─ emit(MessageEndEvent) │
│ │
│ 2. Get LLM Response │
│ ├─ transform_context?(messages) │
│ ├─ convert_to_llm(messages) │
│ └─ stream_function(model, context, config) │
│ │
│ LLM │
│ │ response stream │
│ └─ emit(MessageStartEvent) │
│ └─ emit(MessageUpdateEvent) (streaming) │
│ └─ emit(MessageEndEvent(final_message)) │
│ │
│ 3. Check for Tool Calls │
│ └─ extract_tool_calls(message.content) │
│ │
│ 4. Execute Tools (if any) │
│ ├─ emit(ToolExecutionStartEvent) │
│ ├─ prepareToolCall() │
│ │ ├─ before_tool_call? (block if needed) │
│ │ └─ validateToolArguments() │
│ ├─ execute(tool_call_id, args) │
│ │ Tool │
│ │ │ execute() │
│ │ └─ on_update(partial_result) │
│ ├─ emit(ToolExecutionEndEvent) │
│ └─ createToolResultMessage() │
│ │
│ 5. Emit Turn End Event │
│ └─ emit(TurnEndEvent(message, tool_results)) │
│ │
│ 6. Prepare Next Turn │
│ └─ prepare_next_turn(context) │
│ └─ AgentLoopTurnUpdate │
│ │
│ 7. Check Stop Condition │
│ └─ should_stop_after_turn? (break if true) │
│ │
│ 8. Get Steering/Follow-up Messages │
│ ├─ drain(steering_queue) │
│ └─ drain(follow_up_queue) │
│ │
│ 9. Continue or Break │
│ ├─ alt pending messages exist → continue loop │
│ └─ else → break │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
AgentLoop
│
│ emit(AgentEndEvent(messages))
│
▼
Agent
│
│ appendMessage() → Session
│
▼
User (via promise)
KEY COMPONENTS
Agent Layer
┌──────────────────────────────────────────────────────────────┐
│ Agent │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ state: AgentState │ │
│ │ - system_prompt │ │
│ │ - model │ │
│ │ - messages[] │ │
│ │ - tools[] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Queues: │ │
│ │ - steering_queue: inject after assistant turn │ │
│ │ - follow_up_queue: run when agent stops │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
AgentLoop Layer
┌──────────────────────────────────────────────────────────────┐
│ AgentLoop │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ agentLoop() │ │
│ │ - Main loop execution │ │
│ │ - Tool orchestration │ │
│ │ - Event emission │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ executeToolCallsSequential() │ │
│ │ executeToolCallsParallel() │ │
│ │ - Tool execution coordination │ │
│ │ - Hook invocation │ │
│ │ - Result collection │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Lifecycle Events Flow
AgentStartEvent
│
├─ TurnStartEvent
│ ├─ MessageStartEvent (user/assistant)
│ ├─ MessageEndEvent
│ ├─ ToolExecutionStartEvent
│ │ ├─ ToolExecutionUpdateEvent (streaming)
│ │ └─ ToolExecutionEndEvent
│ └─ TurnEndEvent
│
└─ AgentEndEvent (with final messages)
QUEUE PROCESSING ORDER
- Initial steering messages (if any)
- Main loop:
- Steering/follow-up messages (if any)
- LLM call
- Tool execution (if any)
- Turn end event
- Next turn preparation
- Check stop condition
- Follow-up messages (after loop exits, only if no steering)
DATA FLOW
Input:
User messages → Agent.normalizePromptInput() → AgentLoop
LLM Call:
AgentMessage[] → transform_context() → convert_to_llm() →
LLM.stream() → AssistantMessage
Tool Execution:
ToolCall[] → prepareToolCall() →
before_tool_call? → execute() → after_tool_call? →
ToolResultMessage[]
Output:
AgentEndEvent(messages) → Session.appendMessage() → User promise
EXECUTION MODES
Sequential (EXECUTION_SEQUENTIAL)
ToolCall1 → ToolCall2 → ToolCall3
│ │ │
▼ ▼ ▼
Result1 Result2 Result3
Parallel (EXECUTION_PARALLEL)
ToolCall1 ─┐
ToolCall2──┼→ Execute all → Wait for all → Results
ToolCall3 ─┘