Here's a cleaned-up version with proper alignment: ``` ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ AGENT LOOP DIAGRAM │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ 1. INITIALIZATION │ │ │ │ Agent.start(prompt) │ │ │ │ │ ▼ │ │ normalizePrompt() ← Convert input (String/Message/Vector) to AgentMessage[] │ │ │ │ │ ▼ │ │ runPromptMessages() │ │ │ │ │ ▼ │ └─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ 2. AGENT LOOP START (agentLoop/runAgentLoop) │ │ │ │ emit(AgentStartEvent) │ │ emit(TurnStartEvent) │ │ │ │ for prompt in prompts: │ │ emit(MessageStartEvent(prompt)) │ │ emit(MessageEndEvent(prompt)) │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ 3. MAIN LOOP (while true) │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 4. PENDING MESSAGE HANDLING │ │ │ │ │ │ │ │ │ │ │ │ pending_messages = getSteeringMessages() │ │ │ │ │ │ │ │ │ │ │ │ while has pending_messages OR has_tool_calls: │ │ │ │ │ │ │ │ │ │ │ │ if pending_messages: │ │ │ │ │ │ for msg in pending_messages: │ │ │ │ │ │ emit(MessageStartEvent) │ │ │ │ │ │ emit(MessageEndEvent) │ │ │ │ │ │ push to context.messages │ │ │ │ │ │ pending_messages = [] │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 5. STREAM ASSISTANT RESPONSE │ │ │ │ │ │ │ │ │ │ │ │ a) transform_context (if configured) │ │ │ │ │ │ │ │ │ │ │ │ b) convert_to_llm(messages) │ │ │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Converts AgentMessage[] to Message[] │ │ │ │ │ │ │ │ (filters out compaction/branch summaries, converts bash/custom) │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ c) create Context(system_prompt, llm_messages, tools) │ │ │ │ │ │ │ │ │ │ │ │ d) stream_function(model, context, config) │ │ │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ LLM Stream Events: │ │ │ │ │ │ │ │ • start → create partial AssistantMessage │ │ │ │ │ │ │ │ • text_start/delta/end → update partial message │ │ │ │ │ │ │ │ • thinking_start/delta/end → update partial message │ │ │ │ │ │ │ │ • toolcall_start/delta/end → update partial message │ │ │ │ │ │ │ │ • done → finalize message │ │ │ │ │ │ │ │ • error → handle error │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ e) emit(MessageStartEvent(final_message)) │ │ │ │ │ │ f) emit(MessageEndEvent(final_message)) │ │ │ │ │ │ g) push to context.messages & new_messages │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 6. CHECK STOP REASON │ │ │ │ │ │ │ │ │ │ │ │ if stop_reason in ("error", "aborted"): │ │ │ │ │ │ emit(TurnEndEvent) │ │ │ │ │ │ emit(AgentEndEvent) ← EXIT LOOP │ │ │ │ │ │ return │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 7. EXTRACT TOOL CALLS │ │ │ │ │ │ │ │ │ │ │ │ tool_calls = filter(content, isa ToolCall) │ │ │ │ │ │ │ │ │ │ │ │ if tool_calls: │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ 8. EXECUTE TOOL CALLS │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ if config.tool_execution == SEQUENTIAL OR has_sequential_tool: │ │ │ │ │ │ │ │ executeToolCallsSequential() │ │ │ │ │ │ │ │ else: │ │ │ │ │ │ │ │ executeToolCallsParallel() │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ For EACH tool_call: │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ emit(ToolExecutionStartEvent) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ prepareToolCall(): │ │ │ │ │ │ │ │ │ │ • Find tool by name │ │ │ │ │ │ │ │ │ │ • prepare_arguments (if configured) │ │ │ │ │ │ │ │ │ │ • validateToolArguments │ │ │ │ │ │ │ │ │ │ • before_tool_call hook (if configured) │ │ │ │ │ │ │ │ │ │ └─→ PreparedToolCall("prepared") │ │ │ │ │ │ │ │ │ │ or ImmediateToolCallOutcome("immediate") │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ if "immediate": │ │ │ │ │ │ │ │ │ │ FinalizedToolCallOutcome (synchronous tool) │ │ │ │ │ │ │ │ │ │ else: │ │ │ │ │ │ │ │ │ │ executePreparedToolCall() → ExecutedToolCallOutcome │ │ │ │ │ │ │ │ │ │ finalizeExecutedToolCall() (after_tool_call hook) │ │ │ │ │ │ │ │ │ │ └─→ FinalizedToolCallOutcome │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ emit(ToolExecutionEndEvent) │ │ │ │ │ │ │ │ │ │ createToolResultMessage() │ │ │ │ │ │ │ │ │ │ emitToolResultMessage() │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Append tool_result_messages to context.messages & new_messages │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ emit(TurnEndEvent(message, tool_results)) │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 9. PREPARE NEXT TURN │ │ │ │ │ │ │ │ │ │ │ │ next_turn_context = PrepareNextTurnContext( │ │ │ │ │ │ message, tool_results, current_context, new_messages │ │ │ │ │ │ ) │ │ │ │ │ │ │ │ │ │ │ │ next_turn_snapshot = prepare_next_turn(config, next_turn_context) │ │ │ │ │ │ │ │ │ │ │ │ if !isnothing(next_turn_snapshot): │ │ │ │ │ │ update context, model, thinking_level │ │ │ │ │ │ │ │ │ │ │ │ if should_stop_after_turn(config, next_turn_context): │ │ │ │ │ │ emit(AgentEndEvent) ← EXIT LOOP │ │ │ │ │ │ return │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ pending_messages = getSteeringMessages() ← Check for new steering messages │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ follow_up_messages = getFollowUpMessages() │ │ │ │ if follow_up_messages: │ │ pending_messages = follow_up_messages ← Continue loop to handle follow-ups │ │ continue │ │ │ │ break ← EXIT MAIN LOOP (no more pending messages) │ │ │ │ emit(AgentEndEvent(new_messages)) │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ HELPER STRUCTURES │ │ │ │ AgentMessage (union of): │ │ • UserMessage ("user") │ │ • AssistantMessage ("assistant") │ │ • ToolResultMessage ("toolResult") │ │ • BranchSummaryMessage ("branchSummary") - converted to UserMessage │ │ • CompactionSummaryMessage ("compactionSummary") - converted to UserMessage │ │ • BashExecutionMessage ("bash") - converted to UserMessage if not excluded │ │ • CustomMessage ("custom") - converted to UserMessage │ │ │ │ Message (LLM interface): │ │ • UserMessage (role: "user") │ │ • AssistantMessage (role: "assistant") │ │ • ToolResultMessage (role: "toolResult") │ │ │ │ Steering vs Follow-up: │ │ • Steering: Injected AFTER current assistant turn finishes (can continue conversation) │ │ • Follow-up: Run ONLY after agent would otherwise stop (final messages) │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ DATA FLOW SUMMARY │ │ │ │ User Input LLM Response │ │ │ │ │ │ │ │ │ │ ├──────────► AgentMessage[] ───────────►│ │ │ │ (filter+convert) │ │ │ │ │ │ │ │ convert_to_llm() │ │ │ ├──────────► Message[] ────────────────►│ │ │ │ │ │ │ │ Context │ │ │ │ (system_prompt, │ │ │ │ messages, tools) │ │ │ ├──────────► stream_function() ────────►│ │ │ │ │ │ │ │ │ │ │ │ LLM API │ │ │ │ │ │ │ │ ▼ │ │ │ LLM Stream Events │ │ │ (text_delta, toolcall_delta, ...) │ │ │ │ │ │ │ ▼ │ │ │ AssistantMessage (finalized) │ │ │ │ │ │ │ ▼ │ │ │ check tool_calls? │ │ │ │ │ │ │ │ │ │ │ │ │ YES NO │ │ │ │ │ │ │ │ ▼ │ │ │ │ execute tools │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ emit ToolResultMessage│ │ │ │ │ │ │ │ │ └─────┬─────┘ │ │ │ │ │ │ │ ▼ │ │ │ append to context.messages │ │ │ │ │ │ │ ▼ │ │ │ next turn │ │ │ │ │ │ │ ▼ │ │ │ check pending/follow-up │ │ │ │ │ │ │ ▼ │ │ └───────────────────────────────────────┴───────────────────────────────────────────────────────────────►│ │ (loop back to main loop or end) │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ```