update
This commit is contained in:
+260
-315
@@ -1,325 +1,270 @@
|
|||||||
# Agent Loop Diagram
|
# 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
|
1. **Input Normalization**: User input (String/Message/Vector) → AgentMessage[]
|
||||||
flowchart TD
|
2. **LLM Boundary**: AgentMessage[] → Message[] (filtered, converted for LLM)
|
||||||
Start[Start Agent Loop] --> EmitStart[Emitter: AgentStartEvent]
|
3. **Streaming**: LLM returns event stream → AssistantMessage (built incrementally)
|
||||||
EmitStart --> EmitTurnStart[Emitter: TurnStartEvent]
|
4. **Tool Execution**:
|
||||||
|
- Extract ToolCall objects from AssistantMessage.content
|
||||||
subgraph SteeringQueue "Check Steering Queue"
|
- Execute in parallel (default) or sequential mode
|
||||||
SteeringQueue --> HasSteering{Has pending<br/>steering messages?}
|
- Emit ToolResultMessage for each
|
||||||
HasSteering -- Yes --> ProcessSteering[Process steering messages<br/>Emit MessageStart/End]
|
5. **Message Queues**:
|
||||||
ProcessSteering --> AddToContext[Add to context messages]
|
- Steering: Injected between turns (keeps conversation going)
|
||||||
AddToContext --> PollSteering
|
- Follow-up: Injected after agent stops (final messages)
|
||||||
HasSteering -- No --> PollSteering
|
6. **Context Updates**: All messages appended to context.messages for next iteration
|
||||||
end
|
7. **Loop Control**: Continue until no pending messages AND agent signals stop
|
||||||
|
|
||||||
PollSteering{Next iteration:<br/>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<br/>with error message]
|
|
||||||
CheckStop -- normal --> ExtractTools[Extract ToolCall objects]
|
|
||||||
|
|
||||||
FailTools --> HasMoreTools
|
|
||||||
ExtractTools --> HasMoreTools{Has tool<br/>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<br/>& collect results]
|
|
||||||
|
|
||||||
SeqExec --> SeqFinalize[Finalize tool call<br/> Emit: ToolExecutionEndEvent]
|
|
||||||
ParaExec --> ParaFinalize[Finalize tool calls<br/>Emit: ToolExecutionEndEvent]
|
|
||||||
|
|
||||||
SeqFinalize --> SeqResults[Create ToolResultMessages]
|
|
||||||
ParaFinalize --> ParaResults[Create ToolResultMessages]
|
|
||||||
|
|
||||||
SeqResults --> AddToolResults
|
|
||||||
ParaResults --> AddToolResults[Add tool results to context<br/>Emit: MessageStart/End]
|
|
||||||
end
|
|
||||||
|
|
||||||
AddToolResults --> HasMoreTools
|
|
||||||
|
|
||||||
CheckFollowUp{Has pending<br/>follow-up messages?}
|
|
||||||
CheckFollowUp -- Yes --> ProcessFollowUp[Process follow-up messages<br/>Emit MessageStart/End]
|
|
||||||
ProcessFollowUp --> AddToContext2[Add to context messages]
|
|
||||||
AddToContext2 --> CheckPrepareNext
|
|
||||||
|
|
||||||
CheckPrepareNext{Should prepare<br/>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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Detailed Event Lifecycle
|
## Hook Points
|
||||||
|
|
||||||
```mermaid
|
- `transform_context`: Modify messages before LLM call
|
||||||
sequenceDiagram
|
- `convert_to_llm`: Transform AgentMessage[] to Message[]
|
||||||
participant Agent
|
- `before_tool_call`: Block or modify tool execution
|
||||||
participant Loop as AgentLoop
|
- `after_tool_call`: Modify tool results
|
||||||
participant Emitter as Event Sink
|
- `prepare_next_turn`: Update context/model/thinking level between turns
|
||||||
participant LLM
|
- `should_stop_after_turn`: Signal when agent should terminate
|
||||||
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<br/>assistant turn]
|
|
||||||
SQueue --> SProcess[Processed in<br/>next turn iteration]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Follow-up Queue"
|
|
||||||
follow[followUp(message)] --> FQueue[Queued to run<br/>when agent would stop]
|
|
||||||
FQueue --> FProcess[Processed only<br/>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 |
|
|
||||||
|
|||||||
Reference in New Issue
Block a user