diff --git a/docs/agent_loop_sequence_diagram.md b/docs/agent_loop_sequence_diagram.md index 38ad805..6b1b4ea 100644 --- a/docs/agent_loop_sequence_diagram.md +++ b/docs/agent_loop_sequence_diagram.md @@ -1,221 +1,130 @@ -# Agent Loop Sequence Diagram (ASCII) +# Agent Loop Sequence Diagram ``` -┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ ┌───────────┐ -│ 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) ++---------+ +---------+ +------------+ +-------+ +-------+ +| User | | Agent | | AgentLoop | | LLM | | Tool | ++---------+ +---------+ +------------+ +-------+ +-------+ + | | | | | + | prompt(messages)| | | | + |---------------->--> | | | + | | normalizePrompt()| | | + | |----------------->>| | | + | | runPromptMessages| | | + | |----------------->>| | | + | | | | | + | | createLoopConfig | | | + | |----------------->>| | | + | | | | | + | | agentLoop() | | | + | |----------------->>| | | + | | | emit(AgentStart) | | + | | |------------------> | | + | | | | | + | | | transform_context() | | + | | |------------------> | | + | | | convert_to_llm() | | + | | |------------------> | | + | | | stream_function() | | + | | |-------------------------> | + | | | | response | + | | |<------------------------- | + | | | emit(MessageStart)| | + | | |------------------> | | + | | | emit(MessageEnd) | | + | | |------------------> | | + | | | | | + | | | extract_tool_calls()| | + | | |------------------> | | + | | | | | + | | | executeToolCalls() | | + | | |-------------------------> | + | | | | execute() | + | | |-------------------------> | + | | | | result | + | | |<------------------------- | + | | | emit(ToolExecutionEnd)| | + | | |------------------> | | + | | | createToolResult() | | + | | |------------------> | | + | | | emit(MessageEnd) | | + | | |------------------> | | + | | | | | + | | | emit(TurnEnd) | | + | | |------------------> | | + | | | | | + | | | prepare_next_turn() | | + | | |------------------> | | + | | | | | + | | | should_stop_check() | | + | | |------------------> | | + | | | | | + | | | drain(steering_queue)| | + | | |------------------> | | + | | | | | + | | | drain(follow_up_queue)| | + | | |------------------> | | + | | | | | + | | emit(AgentEnd) | | | + | |------------------>| | | + | | | | | + | | appendMessage() | | | + | |------------------> Session | | + |<----------------| | | | + | messages | | | | + | | | | | ``` -## 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 - -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, only if no steering) - -## DATA FLOW +## Key Participants ``` -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 +User - Initiates conversation by calling prompt() +Agent - High-level wrapper managing state, queues, and events +AgentLoop - Low-level execution engine (agentLoop, runLoop) +LLM - Language model API (via stream_function) +Tool - Tool execution (bash, read, write, edit) +Session - Conversation history persistence ``` -## EXECUTION MODES +## Main Loop Flow -### Sequential (EXECUTION_SEQUENTIAL) ``` -ToolCall1 → ToolCall2 → ToolCall3 - │ │ │ - ▼ ▼ ▼ -Result1 Result2 Result3 +1. [Agent] normalizePromptInput() - Convert input to messages +2. [Agent] runPromptMessages() - Start processing +3. [Agent] createLoopConfig() - Build loop configuration +4. [Agent] agentLoop() - Start low-level loop +5. [AgentLoop] emit(AgentStart) - Signal start +6. [AgentLoop] transform_context() - Optional context transform +7. [AgentLoop] convert_to_llm() - Convert to LLM messages +8. [AgentLoop] stream_function() - Call LLM +9. [AgentLoop] emit(MessageStart/End) - Stream response +10. [AgentLoop] extract_tool_calls() - Check for tool calls +11. [AgentLoop] executeToolCalls() - Execute tools (seq/parallel) +12. [AgentLoop] emit(TurnEnd) - Signal turn complete +13. [AgentLoop] prepare_next_turn() - Optional next turn setup +14. [AgentLoop] should_stop_check() - Check termination +15. [AgentLoop] drain queues() - Check steering/follow-up +16. [AgentLoop] emit(AgentEnd) - Signal completion +17. [Agent] appendMessage() - Persist to session ``` -### Parallel (EXECUTION_PARALLEL) +## Queue Processing + ``` -ToolCall1 ─┐ -ToolCall2──┼→ Execute all → Wait for all → Results -ToolCall3 ─┘ +steering_queue: Injected after assistant turn, before next LLM call +follow_up_queue: Run only when agent would otherwise stop (after loop exit) +``` + +## Event Flow + +``` +AgentStart → [TurnStart → MessageStart → MessageEnd → ...] → TurnEnd → ... + ↓ + AgentEnd +``` + +## Tool Execution Options + +``` +Sequential: One tool at a time, in order +Parallel: All tools spawned concurrently, results collected ```