# Agent Loop Sequence Diagram ```mermaid 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 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 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)