update
This commit is contained in:
+204
-281
@@ -1,298 +1,221 @@
|
||||
# Agent Loop Sequence Diagram
|
||||
# Agent Loop Sequence Diagram (ASCII)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Agent
|
||||
participant AgentLoop
|
||||
participant LLM
|
||||
participant Tool
|
||||
participant Session
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ ┌───────────┐
|
||||
│ User │─────>│ Agent │─────>│ AgentLoop │─────>│ LLM │
|
||||
└─────────────┘ └─────────────┘ └─────────────────┘ └───────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐ ┌─────────────────┐ ┌───────────┐ ┌─────────────┐
|
||||
│ User │<─────│ AgentLoop │─────>│ Agent │─────>│ Session │
|
||||
└─────────────┘ └─────────────────┘ └───────────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ 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 execution
|
||||
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
|
||||
AgentLoop->>AgentLoop: before_tool_call(context, signal)
|
||||
alt Hook blocks
|
||||
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
|
||||
end
|
||||
|
||||
alt Parallel execution
|
||||
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
|
||||
loop Main Agent Loop
|
||||
Note over AgentLoop: Continue loop
|
||||
end
|
||||
else Follow-up messages exist
|
||||
AgentLoop->>AgentLoop: set pending_messages = follow_up
|
||||
loop Main Agent Loop
|
||||
Note over AgentLoop: Continue loop
|
||||
end
|
||||
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
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ AGENT LOOP MAIN FLOW │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
User
|
||||
│
|
||||
│ prompt(messages)
|
||||
▼
|
||||
Agent
|
||||
│
|
||||
│ normalizePromptInput()
|
||||
│ runPromptMessages()
|
||||
│
|
||||
├─ alt steering_queue has messages
|
||||
│ │ drain(steering_queue)
|
||||
│ │ runPromptMessages()
|
||||
│
|
||||
├─ alt follow_up_queue has messages
|
||||
│ │ drain(follow_up_queue)
|
||||
│ │ runPromptMessages()
|
||||
│
|
||||
└─ else (normal prompt)
|
||||
│ createLoopConfig()
|
||||
│ createActiveRun()
|
||||
│
|
||||
▼
|
||||
AgentLoop
|
||||
│
|
||||
│ agentLoop(messages, context, config)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MAIN LOOP │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 1. Handle queued messages │
|
||||
│ ├─ drain(steering_queue) │
|
||||
│ ├─ emit(MessageStartEvent) │
|
||||
│ ├─ push to context.messages │
|
||||
│ └─ emit(MessageEndEvent) │
|
||||
│ │
|
||||
│ 2. Get LLM Response │
|
||||
│ ├─ transform_context?(messages) │
|
||||
│ ├─ convert_to_llm(messages) │
|
||||
│ └─ stream_function(model, context, config) │
|
||||
│ │
|
||||
│ LLM │
|
||||
│ │ response stream │
|
||||
│ └─ emit(MessageStartEvent) │
|
||||
│ └─ emit(MessageUpdateEvent) (streaming) │
|
||||
│ └─ emit(MessageEndEvent(final_message)) │
|
||||
│ │
|
||||
│ 3. Check for Tool Calls │
|
||||
│ └─ extract_tool_calls(message.content) │
|
||||
│ │
|
||||
│ 4. Execute Tools (if any) │
|
||||
│ ├─ emit(ToolExecutionStartEvent) │
|
||||
│ ├─ prepareToolCall() │
|
||||
│ │ ├─ before_tool_call? (block if needed) │
|
||||
│ │ └─ validateToolArguments() │
|
||||
│ ├─ execute(tool_call_id, args) │
|
||||
│ │ Tool │
|
||||
│ │ │ execute() │
|
||||
│ │ └─ on_update(partial_result) │
|
||||
│ ├─ emit(ToolExecutionEndEvent) │
|
||||
│ └─ createToolResultMessage() │
|
||||
│ │
|
||||
│ 5. Emit Turn End Event │
|
||||
│ └─ emit(TurnEndEvent(message, tool_results)) │
|
||||
│ │
|
||||
│ 6. Prepare Next Turn │
|
||||
│ └─ prepare_next_turn(context) │
|
||||
│ └─ AgentLoopTurnUpdate │
|
||||
│ │
|
||||
│ 7. Check Stop Condition │
|
||||
│ └─ should_stop_after_turn? (break if true) │
|
||||
│ │
|
||||
│ 8. Get Steering/Follow-up Messages │
|
||||
│ ├─ drain(steering_queue) │
|
||||
│ └─ drain(follow_up_queue) │
|
||||
│ │
|
||||
│ 9. Continue or Break │
|
||||
│ ├─ alt pending messages exist → continue loop │
|
||||
│ └─ else → break │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
AgentLoop
|
||||
│
|
||||
│ emit(AgentEndEvent(messages))
|
||||
│
|
||||
▼
|
||||
Agent
|
||||
│
|
||||
│ appendMessage() → Session
|
||||
│
|
||||
▼
|
||||
User (via promise)
|
||||
```
|
||||
|
||||
## Key Components
|
||||
## 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
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Agent │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ state: AgentState │ │
|
||||
│ │ - system_prompt │ │
|
||||
│ │ - model │ │
|
||||
│ │ - messages[] │ │
|
||||
│ │ - tools[] │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Queues: │ │
|
||||
│ │ - steering_queue: inject after assistant turn │ │
|
||||
│ │ - follow_up_queue: run when agent stops │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ AgentLoop │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ agentLoop() │ │
|
||||
│ │ - Main loop execution │ │
|
||||
│ │ - Tool orchestration │ │
|
||||
│ │ - Event emission │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ executeToolCallsSequential() │ │
|
||||
│ │ executeToolCallsParallel() │ │
|
||||
│ │ - Tool execution coordination │ │
|
||||
│ │ - Hook invocation │ │
|
||||
│ │ - Result collection │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Lifecycle Events
|
||||
- `AgentStartEvent` / `AgentEndEvent` - Agent lifecycle boundaries
|
||||
- `TurnStartEvent` / `TurnEndEvent` - Conversation turns
|
||||
- `MessageStartEvent` / `MessageEndEvent` - Message processing
|
||||
- `ToolExecutionStartEvent` / `ToolExecutionEndEvent` - Tool execution
|
||||
### Lifecycle Events Flow
|
||||
```
|
||||
AgentStartEvent
|
||||
│
|
||||
├─ TurnStartEvent
|
||||
│ ├─ MessageStartEvent (user/assistant)
|
||||
│ ├─ MessageEndEvent
|
||||
│ ├─ ToolExecutionStartEvent
|
||||
│ │ ├─ ToolExecutionUpdateEvent (streaming)
|
||||
│ │ └─ ToolExecutionEndEvent
|
||||
│ └─ TurnEndEvent
|
||||
│
|
||||
└─ AgentEndEvent (with final messages)
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
## QUEUE PROCESSING ORDER
|
||||
|
||||
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:
|
||||
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)
|
||||
3. **Follow-up messages** (after loop exits, only if no steering)
|
||||
|
||||
## DATA FLOW
|
||||
|
||||
```
|
||||
Input:
|
||||
User messages → Agent.normalizePromptInput() → AgentLoop
|
||||
|
||||
LLM Call:
|
||||
AgentMessage[] → transform_context() → convert_to_llm() →
|
||||
LLM.stream() → AssistantMessage
|
||||
|
||||
Tool Execution:
|
||||
ToolCall[] → prepareToolCall() →
|
||||
before_tool_call? → execute() → after_tool_call? →
|
||||
ToolResultMessage[]
|
||||
|
||||
Output:
|
||||
AgentEndEvent(messages) → Session.appendMessage() → User promise
|
||||
```
|
||||
|
||||
## EXECUTION MODES
|
||||
|
||||
### Sequential (EXECUTION_SEQUENTIAL)
|
||||
```
|
||||
ToolCall1 → ToolCall2 → ToolCall3
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Result1 Result2 Result3
|
||||
```
|
||||
|
||||
### Parallel (EXECUTION_PARALLEL)
|
||||
```
|
||||
ToolCall1 ─┐
|
||||
ToolCall2──┼→ Execute all → Wait for all → Results
|
||||
ToolCall3 ─┘
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user