From b8254490e8558f45072e1227fcc73a5b3842a18b Mon Sep 17 00:00:00 2001 From: narawat Date: Tue, 28 Jul 2026 11:06:52 +0700 Subject: [PATCH] update --- docs/agent_loop_diagram.md | 505 ++++++++++++++++++------------------- 1 file changed, 241 insertions(+), 264 deletions(-) diff --git a/docs/agent_loop_diagram.md b/docs/agent_loop_diagram.md index 92e62cb..8546488 100644 --- a/docs/agent_loop_diagram.md +++ b/docs/agent_loop_diagram.md @@ -1,270 +1,247 @@ -# Agent Loop Diagram +Here's a cleaned-up version with proper alignment: ``` -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ AGENT LOOP │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ 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)) │ │ -│ │ │ │ -│ └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ 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 │ -│ │ convert_to_llm() │ │ -│ ├───────────────────────────────────→ AgentMessage[] ──────────────→ Message[] ──────────→ │ ─────────────────→ AssistantMessage (streamed) │ -│ │ (filter+convert) │ │ -│ │ │ │ │ -│ │ ▼ │ │ -│ │ Context (system_prompt, │ │ -│ │ messages, tools) │ │ -│ │ │ │ -│ │ │ │ -│ │ stream_function() ─────────┤ ─────────→ LLM API │ -│ │ │ │ -│ │ │ │ -│ │ │ │ -│ │ ▼ │ -│ │ LLM Stream Events (text_delta, toolcall_delta, ...) │ -│ │ │ │ -│ │ ▼ │ -│ │ AssistantMessage (finalized) │ -│ │ │ │ -│ │ ▼ │ -│ │ check tool_calls? ──────────→ YES ────────────→ execute tools │ -│ │ │ │ │ -│ │ │ │ │ -│ │ NO │ │ -│ │ │ │ │ -│ │ │ ▼ ▼ -│ │ │ emit ToolResultMessage -│ │ │ │ │ -│ │ │ │ │ -│ │ ▼ ▼ -│ │ append to context.messages ────────────────────────────────→ ────→ next turn -│ │ │ -│ │ ▼ -│ │ check pending/follow-up -│ │ │ -│ │ │ -│ └─────────────────────────────────────────────────────────────────────────────────→ ────────────────────────────────────────────────────────────┘ -│ (loop back to main loop or end) -│ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ 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) │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ``` - -## Key Flow Points - -1. **Input Normalization**: User input (String/Message/Vector) → AgentMessage[] -2. **LLM Boundary**: AgentMessage[] → Message[] (filtered, converted for LLM) -3. **Streaming**: LLM returns event stream → AssistantMessage (built incrementally) -4. **Tool Execution**: - - Extract ToolCall objects from AssistantMessage.content - - Execute in parallel (default) or sequential mode - - Emit ToolResultMessage for each -5. **Message Queues**: - - Steering: Injected between turns (keeps conversation going) - - Follow-up: Injected after agent stops (final messages) -6. **Context Updates**: All messages appended to context.messages for next iteration -7. **Loop Control**: Continue until no pending messages AND agent signals stop - -## Hook Points - -- `transform_context`: Modify messages before LLM call -- `convert_to_llm`: Transform AgentMessage[] to Message[] -- `before_tool_call`: Block or modify tool execution -- `after_tool_call`: Modify tool results -- `prepare_next_turn`: Update context/model/thinking level between turns -- `should_stop_after_turn`: Signal when agent should terminate