diff --git a/docs/agent_loop_diagram.md b/docs/agent_loop_diagram.md
index 2e35f96..92e62cb 100644
--- a/docs/agent_loop_diagram.md
+++ b/docs/agent_loop_diagram.md
@@ -1,325 +1,270 @@
# Agent Loop Diagram
-## Overview
+```
+┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+│ AGENT LOOP │
+└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
-This document describes the agent loop execution flow in AgentCore.jl, showing how the agent processes messages, executes tools, and handles steering/follow-up 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)) │ │
+│ │ │ │
+│ └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
-## Architecture Layers
+┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+│ 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)
+│
+└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
-```
-┌─────────────────────────────────────────────────────────────────────────┐
-│ Agent (High-Level) │
-│ - State management, event streaming, queueing │
-│ - Steering queue (steer()) and Follow-up queue (followUp()) │
-└─────────────────────────────────────────────────────────────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────────────────────────┐
-│ AgentLoop (Low-Level) │
-│ - Core loop execution with tool calling │
-│ - Tool execution (parallel/sequential) │
-│ - Event emission lifecycle │
-└─────────────────────────────────────────────────────────────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────────────────────────┐
-│ LLM Provider │
-│ - Stream function calls the LLM API │
-│ - Returns assistant response (text + tool calls) │
-└─────────────────────────────────────────────────────────────────────────┘
```
-## Main Agent Loop Flow
+## Key Flow Points
-```mermaid
-flowchart TD
- Start[Start Agent Loop] --> EmitStart[Emitter: AgentStartEvent]
- EmitStart --> EmitTurnStart[Emitter: TurnStartEvent]
-
- subgraph SteeringQueue "Check Steering Queue"
- SteeringQueue --> HasSteering{Has pending
steering messages?}
- HasSteering -- Yes --> ProcessSteering[Process steering messages
Emit MessageStart/End]
- ProcessSteering --> AddToContext[Add to context messages]
- AddToContext --> PollSteering
- HasSteering -- No --> PollSteering
- end
-
- PollSteering{Next iteration:
Poll steering queue again}
-
- subgraph AssistantLoop "Assistant Response Loop"
- PollSteering --> StreamResponse[Stream Assistant Response from LLM]
- StreamResponse --> CheckStop{Stop reason?}
-
- CheckStop -- error/aborted --> EmitTurnEnd1[Emit: TurnEndEvent]
- EmitTurnEnd1 --> EmitAgentEnd1[Emit: AgentEndEvent]
- EmitAgentEnd1 --> End1[End Loop]
-
- CheckStop -- length/truncated --> FailTools[Fail all tool calls
with error message]
- CheckStop -- normal --> ExtractTools[Extract ToolCall objects]
-
- FailTools --> HasMoreTools
- ExtractTools --> HasMoreTools{Has tool
calls?}
-
- HasMoreTools -- Yes --> ExecuteTools[Execute Tools]
- HasMoreTools -- No --> CheckFollowUp
-
- subgraph ToolExecution "Tool Execution"
- direction TB
- ExecuteTools --> ExecMode{Execution Mode?}
-
- ExecMode -- Sequential --> SeqLoop[For each tool call:]
- ExecMode -- Parallel --> ParaLoop[For each tool call:]
-
- SeqLoop --> SeqPrepare[Prepare tool call]
- ParaLoop --> ParaPrepare[Prepare tool call]
-
- SeqPrepare --> SeqExec[Execute sequentially]
- ParaPrepare --> ParaExec[Execute in parallel
& collect results]
-
- SeqExec --> SeqFinalize[Finalize tool call
Emit: ToolExecutionEndEvent]
- ParaExec --> ParaFinalize[Finalize tool calls
Emit: ToolExecutionEndEvent]
-
- SeqFinalize --> SeqResults[Create ToolResultMessages]
- ParaFinalize --> ParaResults[Create ToolResultMessages]
-
- SeqResults --> AddToolResults
- ParaResults --> AddToolResults[Add tool results to context
Emit: MessageStart/End]
- end
-
- AddToolResults --> HasMoreTools
-
- CheckFollowUp{Has pending
follow-up messages?}
- CheckFollowUp -- Yes --> ProcessFollowUp[Process follow-up messages
Emit MessageStart/End]
- ProcessFollowUp --> AddToContext2[Add to context messages]
- AddToContext2 --> CheckPrepareNext
-
- CheckPrepareNext{Should prepare
next turn?}
- CheckPrepareNext -- Yes --> PrepareNext[Call prepare_next_turn hook]
- PrepareNext --> UpdateContext[Update context & config]
- UpdateContext --> CheckStop2
- CheckPrepareNext -- No --> CheckStop2
-
- CheckStop2{Stop after turn?}
- CheckStop2 -- Yes --> EmitAgentEnd2[Emit: AgentEndEvent]
- EmitAgentEnd2 --> End2[End Loop]
- CheckStop2 -- No --> PollSteering
- end
-
- CheckFollowUp -- No --> CheckPrepareNext
-```
+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
-## Detailed Event Lifecycle
+## Hook Points
-```mermaid
-sequenceDiagram
- participant Agent
- participant Loop as AgentLoop
- participant Emitter as Event Sink
- participant LLM
- participant Tools
-
- Agent->>Loop: agentLoop(prompts, context, config)
- Loop->>Emitter: AgentStartEvent
- Loop->>Emitter: TurnStartEvent
-
- note over Loop: Process prompts
- Loop->>Emitter: MessageStartEvent(prompt)
- Loop->>Emitter: MessageEndEvent(prompt)
-
- Loop->>LLM: stream_function(model, context, config)
- LLM-->>Loop: AssistantResponse with ToolCalls
-
- note over Loop: Process response
- Loop->>Emitter: MessageStartEvent(assistant)
-
- alt Has Tool Calls
- Loop->>Tools: Execute tools
- Tools-->>Loop: ToolResults
-
- note over Loop: Emit tool execution events
- Loop->>Emitter: ToolExecutionStartEvent
- Loop->>Emitter: ToolExecutionUpdateEvent (if streaming)
- Loop->>Emitter: ToolExecutionEndEvent
-
- Loop->>Emitter: MessageStartEvent(toolResult)
- Loop->>Emitter: MessageEndEvent(toolResult)
- end
-
- Loop->>Emitter: MessageEndEvent(assistant)
- Loop->>Emitter: TurnEndEvent(message, tool_results)
-
- note over Loop: Check for follow-up/steering
- alt Has more work
- Loop->>Loop: Continue loop
- else Done
- Loop->>Emitter: AgentEndEvent(messages)
- end
-```
-
-## Steering vs Follow-up Messages
-
-```mermaid
-graph LR
- subgraph "Main Conversation"
- A[User Message] --> B[Assistant Response]
- B --> C[Tool Execution]
- C --> D[Tool Result]
- D --> E{Decision Point}
- end
-
- E -->|Continue loop| B
- E -->|Stop & check queues| F
-
- subgraph "Steering Queue"
- steer[steer(message)] --> SQueue[Queued after
assistant turn]
- SQueue --> SProcess[Processed in
next turn iteration]
- end
-
- subgraph "Follow-up Queue"
- follow[followUp(message)] --> FQueue[Queued to run
when agent would stop]
- FQueue --> FProcess[Processed only
when no more work]
- end
-
- F --> SProcess
- F --> FProcess
-```
-
-## Tool Execution Modes
-
-### Sequential Execution
-```mermaid
-flowchart LR
- Tool1[Tool Call 1] --> Prepare1[Prepare]
- Prepare1 --> Execute1[Execute]
- Execute1 --> Finalize1[Finalize]
- Finalize1 --> Result1[Tool Result 1]
-
- Result1 --> Prepare2[Prepare]
- Prepare2 --> Execute2[Execute]
- Execute2 --> Finalize2[Finalize]
- Finalize2 --> Result2[Tool Result 2]
-
- note right of Execute1 "Must complete before next tool"
- note right of Execute2 "Tools run one at a time"
-```
-
-### Parallel Execution
-```mermaid
-flowchart LR
- Tool1[Tool Call 1] --> Prepare1[Prepare]
- Tool2[Tool Call 2] --> Prepare2[Prepare]
- Tool3[Tool Call 3] --> Prepare3[Prepare]
-
- Prepare1 --> Execute1[Execute]
- Prepare2 --> Execute2[Execute]
- Prepare3 --> Execute3[Execute]
-
- Execute1 --> Finalize1[Finalize] --> Result1[Tool Result 1]
- Execute2 --> Finalize2[Finalize] --> Result2[Tool Result 2]
- Execute3 --> Finalize3[Finalize] --> Result3[Tool Result 3]
-
- note right of Prepare1 "Preparation can happen"
- note right of Execute1 "All tools execute"
- note right of Finalize1 "Results collected in order"
-```
-
-## Message Conversion at LLM Boundary
-
-```mermaid
-flowchart LR
- subgraph "Agent Messages (Internal)"
- AM1[UserMessage]
- AM2[AssistantMessage]
- AM3[ToolResultMessage]
- AM4[CompactionSummaryMessage]
- AM5[BranchSummaryMessage]
- AM6[CustomMessage]
- end
-
- AM1 --> Convert[convertToLlm]
- AM2 --> Convert
- AM3 --> Convert
- AM4 --> Convert
- AM5 --> Convert
- AM6 --> Convert
-
- Convert --> LM1[UserMessage]
- Convert --> LM2[AssistantMessage]
- Convert --> LM3[ToolResultMessage]
- Convert --> LM4[UserMessage (summary)]
- Convert --> LM5[UserMessage (branch)]
- Convert --> LM6[UserMessage (custom)]
-
- LM1 --> LLM[LLM API]
- LM2 --> LLM
- LM3 --> LLM
- LM4 --> LLM
- LM5 --> LLM
- LM6 --> LLM
-```
-
-## Key Data Structures
-
-### AgentContext
-```julia
-struct AgentContext
- system_prompt::String
- messages::Vector{AgentMessage}
- tools::Union{Vector{AgentTool}, Nothing}
-end
-```
-
-### AgentLoopConfig
-```julia
-struct AgentLoopConfig
- model::Model
- reasoning::Union{ThinkingLevel, Nothing}
- session_id::Union{String, Nothing}
- convert_to_llm::Function
- transform_context::Union{Function, Nothing}
- get_api_key::Union{Function, Nothing}
- get_steering_messages::Function
- get_follow_up_messages::Function
- before_tool_call::Union{Function, Nothing}
- after_tool_call::Union{Function, Nothing}
- prepare_next_turn::Union{Function, Nothing}
- should_stop_after_turn::Function
- tool_execution::ToolExecutionMode
- # ... other options
-end
-```
-
-## Entry Points
-
-1. **`prompt(agent, input)`** - Start new conversation
- - Validates no active run
- - Normalizes input to messages
- - Calls `runPromptMessages`
-
-2. **`continue!(agent)`** - Continue from last message
- - Checks last message is not assistant
- - Drains steering/follow-up queues first
- - Calls agent loop continuation
-
-## Termination Conditions
-
-The agent loop terminates when:
-1. LLM response has stop_reason = "error" or "aborted"
-2. No tool calls and no pending steering/follow-up messages
-3. `should_stop_after_turn` returns true
-4. Agent is aborted via abort signal
-
-## Event Summary
-
-| Event | When Emitted |
-|-------|--------------|
-| `AgentStartEvent` | Loop begins |
-| `TurnStartEvent` | Each conversation turn |
-| `MessageStartEvent` | Message added to context |
-| `MessageEndEvent` | Message fully processed |
-| `MessageUpdateEvent` | Streaming updates |
-| `ToolExecutionStartEvent` | Tool execution begins |
-| `ToolExecutionUpdateEvent` | Tool execution progress |
-| `ToolExecutionEndEvent` | Tool execution completes |
-| `TurnEndEvent` | Turn completes |
-| `AgentEndEvent` | Loop terminates |
+- `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