This commit is contained in:
2026-07-30 09:28:19 +07:00
parent a541905b72
commit 244cfc4b96
7 changed files with 1262 additions and 225 deletions
+308 -56
View File
@@ -405,87 +405,295 @@
└── Manages ──► PromptTemplates └── Manages ──► PromptTemplates
``` ```
## Data Flow ## Data Flow with Type Transformations
### Complete User Input → Conversation History Flow
``` ```
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
Data Flow Between Layers Level 1: User Input
└─────────────────────────────────────────────────────────────────────────────┘
User Input
• String: "Hello, what's in the directory?"
• AgentMessage: UserMessage(...)
• Vector{AgentMessage}: [UserMessage(...), AssistantMessage(...)]
┌──────────────────────────────────────────────────────────────┐
│ Agent.prompt() / normalizePromptInput() │
│ │
│ Type Dispatch: │
│ • String → UserMessage("user", [TextContent(input)], ts) │
│ • AgentMessage → [input] (wrap in array) │
│ • Vector{AgentMessage} → input (pass-through) │
│ │
│ Output: Vector{AgentMessage} │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ AgentState.messages (AgentMessage[]) │
│ │
│ AgentMessage Types: │
│ • UserMessage (role: "user") │
│ • AssistantMessage (role: "assistant") │
│ • ToolResultMessage (role: "toolResult") │
│ • BashExecutionMessage (custom) │
│ • CompactionSummaryMessage (custom) │
│ • BranchSummaryMessage (custom) │
└──────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Level 2: AgentLoop Processing │
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
User Input (String/Message) ┌──────────────────────────────────────────────────────────────┐
│ transform_context() (optional hook) │
│ │
│ Input: Vector{AgentMessage} │
│ Output: Vector{AgentMessage} (transformed) │
│ - Can truncate, filter, or modify messages │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌────────────────────────────────────────────────────────────────
Agent.prompt() convertToLlm() - Type Transformation Pipeline
- normalizeInput()
└──────────────────────┘ │ Input: Vector{AgentMessage} │
│ Output: Vector{Message} (for LLM API) │
│ │
│ Single Dispatch Mapping: │
│ • UserMessage → UserMessage (pass-through) │
│ • AssistantMessage → AssistantMessage (pass-through) │
│ • ToolResultMessage → ToolResultMessage (pass-through) │
│ │
│ Custom Message Conversions: │
│ • BashExecutionMessage → UserMessage (via bashExecutionToText)│
│ • CompactionSummaryMessage → UserMessage (wrapped) │
│ • BranchSummaryMessage → UserMessage (wrapped) │
└────────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────────────────────────────────────────────
AgentState.messages │ ──► AgentMessage[] Context for LLM API │
└──────────────────────┘ │ - system_prompt: String │
│ - messages: Vector{Message} │
│ - tools: Vector{AgentTool} │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────────────────────────────────────────────
AgentLoop LLM API Call (stream_fn)
- transform_context
└──────────────────────┘ │ Input: model, context, config │
│ Output: Stream{AssistantMessageEvent} │
│ • StartEvent: partial AssistantMessage │
│ • TextStartEvent/TextDeltaEvent/TextEndEvent │
│ • ToolCallStartEvent/ToolCallDeltaEvent/ToolCallEndEvent │
│ • DoneEvent: final AssistantMessage with usage │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────────────────────────────────────────────
convertToLlm() ──► Transforms AgentMessage[] to Message[] AssistantMessage (returned from LLM)
└──────────────────────┘ │ │
│ • role: "assistant" │
│ • content: Vector{MessageContent} │
│ └─ Contains: TextContent[] and/or ToolCall[] │
│ • api, provider, model: String │
│ • usage: Usage (input, output, cache_read, cache_write) │
│ • stop_reason: String ("done", "length", "error", etc.) │
│ • error_message: Union{String, Nothing} │
│ • timestamp: Timestamp (Int64) │
└──────────────────────────────────────────────────────────────┘
├─► Append to AgentState.messages (AssistantMessage)
┌──────────────────────┐ ┌──────────────────────────────────────────────────────────────
LLM API (StreamFn) executeToolCalls() - Tool Processing
- Context: Message[]
└──────────────────────┘ │ Extract: filter(c -> c isa ToolCall, assistant.content) │
│ Output: ExecutedToolCallBatch │
│ • messages: Vector{ToolResultMessage} │
│ • terminate: Bool │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────────────────────────────────────────────
Response (Streaming) ToolResultMessage (for each ToolCall)
- Text deltas
- Tool call deltas • role: "toolResult"
└──────────────────────┘ │ • tool_call_id: String (matches ToolCall.id) │
│ • tool_name: String (matches ToolCall.name) │
│ • content: Vector{MessageContent} │
│ • details: Any (tool-specific) │
│ • usage: Union{Usage, Nothing} │
│ • added_tool_names: Union{Vector{String}, Nothing} │
│ • is_error: Bool │
│ • timestamp: Timestamp (Int64) │
└──────────────────────────────────────────────────────────────┘
├─► Append to AgentState.messages (ToolResultMessage)
┌──────────────────────┐ ┌──────────────────────────────────────────────────────────────
AssistantMessage Updated AgentState.messages (AgentMessage[])
- content: Message[]
└──────────────────────┘ │ Conversation History: │
│ [UserMessage, AssistantMessage, ToolResultMessage, ...] │
└──────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Level 3: Session Storage (optional, for persistence) │
└─────────────────────────────────────────────────────────────────────────────┘
AgentState.messages (Vector{AgentMessage})
┌──────────────────────┐ ┌──────────────────────────────────────────────────────────────
AgentState.messages │ ──► Appended to conversation Session Storage (JSONL) │
└──────────────────────┘ │ │
│ SessionTreeEntry Types: │
│ • MessageEntry (agent_message) │
│ • CompactionEntry (summary, tokens_before) │
│ • BranchSummaryEntry (from_id, summary) │
│ • ModelChangeEntry (provider, model_id) │
│ • ThinkingLevelChangeEntry (thinking_level) │
│ • ActiveToolsChangeEntry (active_tool_names) │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────────────────────────────────────────────
Tool Execution Persisted Data (JSON format)
│ - Extract ToolCalls - Each entry has: id, parent_id, timestamp, type
│ - Execute tools - MessageEntry contains full AgentMessage
└──────────────────────┘ └──────────────────────────────────────────────────────────────
┌──────────────────────┐
│ ToolResultMessage[] │
└──────────────────────┘
┌──────────────────────┐
│ AgentState.messages │ ──► Tool results appended
└──────────────────────┘
│ (Loop back to LLM or end)
┌──────────────────────┐
│ Session Storage │
│ - JSONL format │
│ - Tree entries │
└──────────────────────┘
``` ```
### Tool Call Execution Flow (Detailed)
```
ToolCall (from AssistantMessage.content)
├─ type: "tool"
├─ id: "tc_abc123"
├─ name: "bash"
├─ arguments: Dict("command" => "ls -la")
└─ partial_json: nothing
┌──────────────────────────────────────────────────────────────┐
│ prepareToolCall() │
│ │
│ Input: tool_call::ToolCall │
│ Output: Union{PreparedToolCall, ImmediateToolCallOutcome} │
│ │
│ Steps: │
│ 1. Find tool by name in current_context.tools │
│ 2. before_tool_call hook (optional) │
│ Input: BeforeToolCallContext │
│ Output: BeforeToolCallResult (block, reason) or nothing │
│ 3. prepareToolCallArguments() (optional) │
│ Input: tool_call.arguments::Dict │
│ Output: prepared_arguments::Any │
│ 4. validateToolArguments() (optional) │
│ Input: prepared_tool_call.arguments │
│ Output: validated_args::Any │
│ 5. Return: PreparedToolCall(kind, tool_call, tool, args) │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ executePreparedToolCall() (if prepared) │
│ │
│ Input: PreparedToolCall │
│ Output: ExecutedToolCallOutcome │
│ │
│ tool.execute(tool_call.id, args, signal, on_update) │
│ │ │
│ └─ Returns: AgentToolResultMutable │
│ • content::Vector{MessageContent} │
│ • details::Any │
│ • usage::Union{Usage, Nothing} │
│ • added_tool_names::Union{Vector{String}, Nothing} │
│ • terminate::Union{Bool, Nothing} │
└──────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ finalizeExecutedToolCall() │
│ │
│ Input: ExecutedToolCallOutcome │
│ Output: FinalizedToolCallOutcome │
│ │
│ Steps: │
│ 1. after_tool_call hook (optional) │
│ Input: AfterToolCallContext │
│ Output: AfterToolCallResult (patches) │
│ 2. Apply patches to result │
│ 3. Return: FinalizedToolCallOutcome(tool_call, result, error)│
└───────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ createToolResultMessage() │
│ │
│ Input: FinalizedToolCallOutcome │
│ Output: ToolResultMessage │
│ │
│ Fields: │
│ • role: "toolResult" │
│ • tool_call_id: tool_call.id │
│ • tool_name: tool_call.name │
│ • content: result.content │
│ • details: result.details │
│ • usage: result.usage │
│ • added_tool_names: result.added_tool_names │
│ • is_error: is_error │
│ • timestamp: Int64(Dates.now(Dates.UTC).datetime) │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Emit: ToolResultMessage to conversation │
└──────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Summary of Type Transformations │
└─────────────────────────────────────────────────────────────────────────────┘
User Input (String)
├─► normalizePromptInput()
│ └─► UserMessage (AgentMessage subtype)
Vector{AgentMessage}
├─► transform_context() (optional)
│ └─► Vector{AgentMessage} (transformed)
├─► convertToLlm()
│ └─► Vector{Message} (LLM API format)
│ ├── UserMessage (pass-through)
│ ├── AssistantMessage (pass-through)
│ ├── ToolResultMessage (pass-through)
│ └── Custom messages → UserMessage
AssistantMessage (from LLM)
├─► executeToolCalls()
│ └─► ToolResultMessage[]
ToolResultMessage[]
└─► Appended to AgentState.messages
└─► Vector{AgentMessage} (updated conversation history)
```
## Summary
## Summary ## Summary
The AgentCore.jl architecture follows a clean separation of concerns: The AgentCore.jl architecture follows a clean separation of concerns:
@@ -495,4 +703,48 @@ The AgentCore.jl architecture follows a clean separation of concerns:
3. **AgentLoop** - Core LLM interaction loop 3. **AgentLoop** - Core LLM interaction loop
4. **Session** - Conversation history management 4. **Session** - Conversation history management
### Data Transformation Summary
```
Input Type Flow:
User Input (String/Message)
├─ normalizePromptInput()
│ └─► Vector{AgentMessage}
├─ transform_context() (optional)
│ └─► Vector{AgentMessage} (transformed)
├─ convertToLlm()
│ └─► Vector{Message} (LLM API format)
├─ LLM API (stream_fn)
│ └─► AssistantMessage
├─ executeToolCalls()
│ └─► ToolResultMessage[]
└─► Vector{AgentMessage} (final conversation)
```
### Key Data Flow Patterns
1. **Message Transformation**: `AgentMessage[] → Message[]` via `convertToLlm()`
- UserMessage → UserMessage (pass-through)
- AssistantMessage → AssistantMessage (pass-through)
- ToolResultMessage → ToolResultMessage (pass-through)
- Custom messages (Bash, Compaction, Branch) → UserMessage
2. **Tool Execution**: `ToolCall → ToolResultMessage`
- prepareToolCall() validates and prepares
- execute() runs the tool
- finalize() applies hooks and returns outcome
- createToolResultMessage() creates result entry
3. **Event Streaming**: `Stream{Event}` with lifecycle events
- AgentStartEvent, TurnStartEvent
- MessageStartEvent, MessageUpdateEvent, MessageEndEvent
- ToolExecutionStartEvent, ToolExecutionEndEvent
- TurnEndEvent, AgentEndEvent
Each layer transforms data and passes it to the next layer, with clear interfaces and event hooks for customization. Each layer transforms data and passes it to the next layer, with clear interfaces and event hooks for customization.
+24 -5
View File
@@ -243,16 +243,35 @@ followUp(agent, UserMessage(...))
```julia ```julia
# Transform messages before sending to LLM # Transform messages before sending to LLM
function myConvertToLlm(messages::Vector{AgentMessage}) function myConvertToLlm(messages::Vector{AgentMessage})::Vector{Message}
return filter( result::Vector{Message} = Message[]
m -> m.role in ["user", "assistant", "toolResult"], for m in messages
messages converted = convertToLlmMessage(m)
) if !isnothing(converted)
push!(result, converted)
end
end
return result
end end
agent = Agent(Dict(:convertToLlm => myConvertToLlm)) agent = Agent(Dict(:convertToLlm => myConvertToLlm))
``` ```
**Data Flow**:
```
Vector{AgentMessage}
│ convertToLlmMessage() dispatches on type:
│ • UserMessage → UserMessage (pass-through)
│ • AssistantMessage → AssistantMessage (pass-through)
│ • ToolResultMessage → ToolResultMessage (pass-through)
│ • BashExecutionMessage → UserMessage (bashExecutionToText)
│ • CompactionSummaryMessage → UserMessage (wrapped)
│ • BranchSummaryMessage → UserMessage (wrapped)
Vector{Message} (for LLM API)
```
#### transform_context #### transform_context
```julia ```julia
+347 -83
View File
@@ -27,11 +27,16 @@ agentLoopContinue()
└─ Returns: EventStream └─ Returns: EventStream
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
Internal Flow Data Flow with Type Transformations
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. runAgentLoop() ── Entry point for new conversation │ │ 1. runAgentLoop() ── Entry point for new conversation │
│ Input: prompts::Vector{AgentMessage} │
│ context::AgentContext (system_prompt, messages, tools) │
│ config::AgentLoopConfig │
│ Output: new_messages::Vector{AgentMessage} (appended prompts + turns) │
│ │
│ - Creates copy of prompts │ │ - Creates copy of prompts │
│ - Appends prompts to context.messages │ │ - Appends prompts to context.messages │
│ - Emits AgentStartEvent │ │ - Emits AgentStartEvent │
@@ -43,12 +48,20 @@ agentLoopContinue()
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. runLoop() ── Main event loop │ │ 2. runLoop() ── Main event loop │
│ Input: current_context::AgentContext │
│ new_messages::Vector{AgentMessage} │
│ Output: N/A (writes to new_messages and context.messages) │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │ │ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ while true: │ │ │ │ while true: │ │
│ │ 1. Get steering/follow-up messages (if any) │ │ │ │ 1. Get steering/follow-up messages (if any) │ │
│ │ 2. Emit messages as UserMessage │ │ │ │ 2. Emit messages as UserMessage │ │
│ │ 3. streamAssistantResponse() │ │ │ │ 3. streamAssistantResponse() │ │
│ │ - Input: context.messages::Vector{AgentMessage} │ │
│ │ - Output: message::AssistantMessage │ │
│ │ 4. Execute tool calls (sequential or parallel) │ │ │ │ 4. Execute tool calls (sequential or parallel) │ │
│ │ - Input: AssistantMessage with ToolCall[] │ │
│ │ - Output: tool_results::Vector{ToolResultMessage} │ │
│ │ 5. Emit TurnEndEvent │ │ │ │ 5. Emit TurnEndEvent │ │
│ │ 6. prepare_next_turn (optional) │ │ │ │ 6. prepare_next_turn (optional) │ │
│ │ 7. should_stop_after_turn? (check termination) │ │ │ │ 7. should_stop_after_turn? (check termination) │ │
@@ -59,27 +72,89 @@ agentLoopContinue()
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. streamAssistantResponse() ── LLM interaction │ │ 3. streamAssistantResponse() ── LLM interaction │
- transform_context (optional) Input: context::AgentContext
- convert_to_llm (transform to Message[]) config::AgentLoopConfig
- Call stream_fn (LLM API) Output: message::AssistantMessage
- Stream response deltas
- Emit MessageStart/Update/End events Data Transformations:
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Step 1: transform_context (optional) │ │
│ │ Input: context.messages::Vector{AgentMessage} │ │
│ │ Output: messages::Vector{AgentMessage} (transformed) │ │
│ │ │ │
│ │ Step 2: convert_to_llm │ │
│ │ Input: messages::Vector{AgentMessage} │ │
│ │ Output: llm_messages::Vector{Message} │ │
│ │ - UserMessage → UserMessage │ │
│ │ - AssistantMessage → AssistantMessage │ │
│ │ - ToolResultMessage → ToolResultMessage │ │
│ │ - BashExecutionMessage → UserMessage │ │
│ │ - CompactionSummaryMessage → UserMessage │ │
│ │ - BranchSummaryMessage → UserMessage │ │
│ │ │ │
│ │ Step 3: Call stream_fn │ │
│ │ Input: model, llm_context::Context, config │ │
│ │ Output: response::Stream (events) │ │
│ │ │ │
│ │ Step 4: Stream events │ │
│ │ Events: start, text_start/delta/end, toolcall_start/delta/end │ │
│ │ Final: AssistantMessage (with ToolCall[] in content) │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. executeToolCalls() ── Tool execution │ │ 4. executeToolCalls() ── Tool execution │
│ Input: assistant_message::AssistantMessage (contains ToolCall[]) │
│ current_context::AgentContext │
│ Output: ExecutedToolCallBatch (messages::ToolResultMessage[], terminate) │
│ │
│ For each ToolCall: │
│ ┌────────────────────────────────────────────────────────────────────┐ │ │ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ if EXECUTION_SEQUENTIAL || has_sequential_tool: │ │ │ │ prepareToolCall() │ │
│ │ executeToolCallsSequential() │ │ │ │ Input: tool_call::ToolCall │ │
│ │ else: │ │ │ │ Output: PreparedToolCall or ImmediateToolCallOutcome │ │
│ │ executeToolCallsParallel() │ │ │ │ - validates arguments │ │
│ │ - runs before_tool_call hook (optional) │ │
│ │ - runs prepare_arguments hook (optional) │ │
│ │ │ │
│ │ executePreparedToolCall() (if prepared) │ │
│ │ Input: PreparedToolCall │ │
│ │ Output: ExecutedToolCallOutcome │ │
│ │ - calls tool.execute() │ │
│ │ - returns AgentToolResultMutable │ │
│ │ │ │
│ │ finalizeExecutedToolCall() │ │
│ │ Input: ExecutedToolCallOutcome │ │
│ │ Output: FinalizedToolCallOutcome │ │
│ │ - runs after_tool_call hook (optional) │ │
│ │ - returns ToolCall + AgentToolResultMutable + is_error │ │
│ │ │ │
│ │ createToolResultMessage() │ │
│ │ Input: FinalizedToolCallOutcome │ │
│ │ Output: ToolResultMessage │ │
│ │ - role: "toolResult" │ │
│ │ - tool_call_id, tool_name, content, details │ │
│ │ - usage, added_tool_names, is_error, timestamp │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Sequential: executeToolCallsSequential() │ │
│ │ - Executes tools one at a time, waits for each │ │
│ │ - Returns batch of ToolResultMessage[] │ │
│ │ │ │
│ │ Parallel: executeToolCallsParallel() │ │
│ │ - Creates closures for async execution │ │
│ │ - Executes all closures, collects results │ │
│ │ - Returns batch of ToolResultMessage[] │ │
│ └────────────────────────────────────────────────────────────────────┘ │ │ └────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. AgentEndEvent ── Final event with all messages │ │ 5. AgentEndEvent ── Final event with all messages │
│ Output: messages::Vector{AgentMessage} │
│ Contains: [UserMessage, AssistantMessage, ToolResultMessage, ...] │
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
``` ```
@@ -301,17 +376,44 @@ function streamAssistantResponse(
)::AssistantMessage )::AssistantMessage
``` ```
**Flow**: **Data Flow**:
1. Get messages from context
2. Apply transform_context (optional) ```
3. Convert to LLM messages with convert_to_llm Input: context.messages::Vector{AgentMessage}
4. Create Context object
5. Resolve API key [transform_context] (optional hook)
6. Call stream_fn with model, context, and config
7. Stream events: messages::Vector{AgentMessage}
- "start" → MessageStartEvent
- "text_start", "text_delta", "text_end" → MessageUpdateEvent [convert_to_llm] - Type transformation pipeline
- "done", "error" → MessageEndEvent
llm_messages::Vector{Message}
│ AgentMessage → Message mapping:
│ • UserMessage → UserMessage (pass-through)
│ • AssistantMessage → AssistantMessage (pass-through)
│ • ToolResultMessage → ToolResultMessage (pass-through)
│ • BashExecutionMessage → UserMessage (text conversion)
│ • CompactionSummaryMessage → UserMessage (text wrapped)
│ • BranchSummaryMessage → UserMessage (text wrapped)
Context(system_prompt, llm_messages, tools)
stream_fn(model, context, config) - LLM API call
Stream of AssistantMessageEvent:
• StartEvent (partial AssistantMessage)
• TextStartEvent/TextDeltaEvent/TextEndEvent
• ToolCallStartEvent/ToolCallDeltaEvent/ToolCallEndEvent
• DoneEvent (final AssistantMessage with usage, stop_reason)
Return: AssistantMessage
- content::Vector{MessageContent}
- usage::Usage
- stop_reason::String
- Contains ToolCall[] if tool calls requested
```
### executeToolCalls() ### executeToolCalls()
@@ -320,27 +422,56 @@ function executeToolCalls(
current_context::AgentContext, current_context::AgentContext,
assistant_message::AssistantMessage, assistant_message::AssistantMessage,
config::AgentLoopConfig, config::AgentLoopConfig,
signal::Union{Nothing, AbortSignal}, signal::Union{Nothing, AbortSignal>,
emit::AgentEventSink, emit::AgentEventSink,
)::ExecutedToolCallBatch )::ExecutedToolCallBatch
``` ```
**Logic**: **Data Flow**:
```julia
tool_calls = filter(c -> c isa ToolCall, assistant_message.content)
# Check if any tool requires sequential execution ```
has_sequential = any(tc -> begin Input: assistant_message::AssistantMessage
tool = findfirst(t -> t.name == tc.name, current_context.tools) - content::Vector{MessageContent}
!isnothing(tool) && tool.execution_mode == EXECUTION_SEQUENTIAL └─ Contains ToolCall[] and/or TextContent[]
end, tool_calls)
filter(c -> c isa ToolCall, assistant_message.content)
# Determine execution mode
if config.tool_execution == EXECUTION_SEQUENTIAL || has_sequential tool_calls::Vector{ToolCall}
executeToolCallsSequential(...) - type: "tool"
else - id::String
executeToolCallsParallel(...) - name::String
end - arguments::Dict{String, Any}
- partial_json::Union{String, Nothing}
Check execution mode:
• config.tool_execution (sequential/parallel)
• Any tool.execution_mode == EXECUTION_SEQUENTIAL?
┌─────────────────────────────────────────────────────────────────┐
│ Sequential Mode (or has_sequential_tool) │
│ For each tool_call in tool_calls: │
│ prepareToolCall() → PreparedToolCall │
│ executePreparedToolCall() → ExecutedToolCallOutcome │
│ finalizeExecutedToolCall() → FinalizedToolCallOutcome │
│ createToolResultMessage() → ToolResultMessage │
│ (wait for completion before next tool) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Parallel Mode │
│ For each tool_call in tool_calls: │
│ prepareToolCall() → (PreparedToolCall | ImmediateOutcome) │
│ If prepared: create closure │
│ If immediate: execute and add to finalized_calls │
│ │
│ For each entry in finalized_calls: │
│ If closure: execute closure │
│ If finalized: use as-is │
└─────────────────────────────────────────────────────────────────┘
ExecutedToolCallBatch
- messages::Vector{ToolResultMessage}
- terminate::Bool (true if all tools have terminate=true)
``` ```
### executeToolCallsSequential() ### executeToolCallsSequential()
@@ -400,29 +531,77 @@ function prepareToolCall(
)::Union{PreparedToolCall, ImmediateToolCallOutcome} )::Union{PreparedToolCall, ImmediateToolCallOutcome}
``` ```
**Flow**: **Data Flow**:
1. Find tool by name
2. If not found → ImmediateToolCallOutcome (error) ```
3. before_tool_call hook (optional) Input: tool_call::ToolCall
4. prepareToolCallArguments() (optional) - id::String
5. validateToolArguments() - name::String
6. Return PreparedToolCall - arguments::Dict{String, Any}
findfirst(t -> t.name == tool_call.name, current_context.tools)
If tool is nothing:
→ ImmediateToolCallOutcome("immediate", error_result, is_error=true)
If tool exists:
[before_tool_call hook] (optional)
Input: BeforeToolCallContext(assistant_message, tool_call, args, context)
Output: BeforeToolCallResult (block, reason) or nothing
If block=true → ImmediateToolCallOutcome(error)
prepareToolCallArguments(tool, tool_call)
Input: tool_call.arguments::Dict{String, Any}
Output: prepared_arguments::Any
(Optional: transform arguments before validation)
validateToolArguments(tool, prepared_tool_call)
Input: prepared_tool_call.arguments
Output: validated_args::Any
(Optional: JSON schema validation)
Return: PreparedToolCall("prepared", tool_call, tool, validated_args)
- kind: "prepared"
- tool_call: ToolCall (original)
- tool: AgentTool
- args: validated arguments
```
### executePreparedToolCall() ### executePreparedToolCall()
```julia ```julia
function executePreparedToolCall( function executePreparedToolCall(
prepared::PreparedToolCall, prepared::PreparedToolCall,
signal::Union{Nothing, AbortSignal}, signal::Union{Nothing, AbortSignal>,
emit::AgentEventSink, emit::AgentEventSink,
)::ExecutedToolCallOutcome )::ExecutedToolCallOutcome
``` ```
**Flow**: **Data Flow**:
1. Call tool.execute(id, args, signal, on_update)
2. Collect update events (if any) ```
3. Wait for all update events Input: prepared::PreparedToolCall
4. Return ExecutedToolCallOutcome(result) - tool_call::ToolCall
- tool::AgentTool
- args::Any (validated)
tool.execute(tool_call.id, args, signal, on_update)
Input: tool_call_id::String
args::Any
signal::Union{Any, Nothing}
on_update::Function (partial_result → void)
Output: AgentToolResultMutable
- content::Vector{MessageContent}
- details::Any
- usage::Union{Usage, Nothing}
- added_tool_names::Union{Vector{String}, Nothing}
- terminate::Union{Bool, Nothing}
Collect update events from on_update callbacks
Return: ExecutedToolCallOutcome(result, is_error=false)
- result::AgentToolResultMutable
```
### finalizeExecutedToolCall() ### finalizeExecutedToolCall()
@@ -433,13 +612,43 @@ function finalizeExecutedToolCall(
prepared::PreparedToolCall, prepared::PreparedToolCall,
executed::ExecutedToolCallOutcome, executed::ExecutedToolCallOutcome,
config::AgentLoopConfig, config::AgentLoopConfig,
signal::Union{Nothing, AbortSignal}, signal::Union{Nothing, AbortSignal>,
)::FinalizedToolCallOutcome )::FinalizedToolCallOutcome
``` ```
**Flow**: **Data Flow**:
1. after_tool_call hook (optional)
2. Return FinalizedToolCallOutcome ```
Input: executed::ExecutedToolCallOutcome
- result::AgentToolResultMutable
- is_error::Bool
[after_tool_call hook] (optional)
Input: AfterToolCallContext(
assistant_message,
tool_call,
args,
result,
is_error,
context
)
Output: AfterToolCallResult (optional patches)
- content::Union{Vector{MessageContent}, Nothing}
- details::Union{Any, Nothing}
- is_error::Union{Bool, Nothing}
- usage::Union{Usage, Nothing}
- terminate::Union{Bool, Nothing}
Apply patches to result (if any)
result.content = result.content patches.content
result.details = result.details patches.details
is_error = is_error patches.is_error
Return: FinalizedToolCallOutcome
- tool_call::ToolCall (original)
- result::AgentToolResultMutable (final)
- is_error::Bool
```
### createToolResultMessage() ### createToolResultMessage()
@@ -449,19 +658,39 @@ function createToolResultMessage(
)::ToolResultMessage )::ToolResultMessage
``` ```
**Creates**: **Data Flow**:
```julia
ToolResultMessage( ```
"toolResult", Input: finalized::FinalizedToolCallOutcome
finalized.tool_call.id, - tool_call::ToolCall
finalized.tool_call.name, - result::AgentToolResultMutable
finalized.result.content, - content::Vector{MessageContent}
finalized.result.details, - details::Any
finalized.result.usage, - usage::Union{Usage, Nothing}
finalized.result.added_tool_names, - added_tool_names::Union{Vector{String}, Nothing}
finalized.is_error, - is_error::Bool
timestamp,
) Build ToolResultMessage:
• role: "toolResult"
• tool_call_id: tool_call.id
• tool_name: tool_call.name
• content: result.content
• details: result.details
• usage: result.usage
• added_tool_names: result.added_tool_names
• is_error: is_error
• timestamp: Int64(Dates.now(Dates.UTC).datetime)
Output: ToolResultMessage
- role::String ("toolResult")
- tool_call_id::String
- tool_name::String
- content::Vector{MessageContent}
- details::Any
- usage::Union{Usage, Nothing}
- added_tool_names::Union{Vector{String}, Nothing}
- is_error::Bool
- timestamp::Timestamp (Int64)
``` ```
## Execution Modes ## Execution Modes
@@ -650,35 +879,70 @@ AgentStartEvent
### 1. Message Transformation Pipeline ### 1. Message Transformation Pipeline
``` ```
AgentMessage[] (internal) Vector{AgentMessage} (internal conversation history)
transform_context() ├─ transform_context() (optional hook)
│ Input: Vector{AgentMessage}
AgentMessage[] (transformed) │ Output: Vector{AgentMessage} (transformed)
convert_to_llm() └─ convert_to_llm()
Message[] (LLM API) │ Type mapping (single dispatch):
│ • UserMessage → UserMessage (pass-through)
│ • AssistantMessage → AssistantMessage (pass-through)
│ • ToolResultMessage → ToolResultMessage (pass-through)
│ • BashExecutionMessage → UserMessage (text conversion)
│ • CompactionSummaryMessage → UserMessage (text wrapped)
│ • BranchSummaryMessage → UserMessage (text wrapped)
Vector{Message} (for LLM API)
``` ```
### 2. Tool Call Lifecycle ### 2. Tool Call Lifecycle (with Data Transformations)
``` ```
ToolCall (in assistant message) ToolCall (in AssistantMessage.content)
├─ before_tool_call (hook) ├─ before_tool_call hook (optional)
│ Input: BeforeToolCallContext(
│ assistant_message::AssistantMessage,
│ tool_call::ToolCall,
│ args::Dict{String, Any},
│ context::AgentContext
│ )
│ Output: BeforeToolCallResult (block, reason) or nothing
├─ prepareToolCall() ├─ prepareToolCall()
├─ validate arguments Input: tool_call::ToolCall
└─ prepare arguments (optional) Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
│ • PreparedToolCall (kind, tool_call, tool, args)
│ • ImmediateToolCallOutcome (immediate, result, is_error)
├─ execute() ├─ executePreparedToolCall() (if prepared)
├─ Immediate: return result Input: PreparedToolCall
└─ Prepared: async execution Output: ExecutedToolCallOutcome
│ tool.execute() returns AgentToolResultMutable
│ • content::Vector{MessageContent}
│ • details::Any
│ • usage::Union{Usage, Nothing}
│ • terminate::Union{Bool, Nothing}
├─ after_tool_call (hook) ├─ finalizeExecutedToolCall()
│ Input: ExecutedToolCallOutcome
│ Output: FinalizedToolCallOutcome
│ • tool_call::ToolCall
│ • result::AgentToolResultMutable
│ • is_error::Bool
└─ createToolResultMessage() └─ createToolResultMessage()
Input: FinalizedToolCallOutcome
Output: ToolResultMessage
• role: "toolResult"
• tool_call_id, tool_name
• content::Vector{MessageContent}
• details, usage, added_tool_names
• is_error, timestamp
``` ```
### 3. Turn Termination ### 3. Turn Termination
+274 -15
View File
@@ -98,6 +98,52 @@
## Message Types ## Message Types
### Type Hierarchy
```
Message (for LLM API)
├── UserMessage (role: "user")
│ ├── content::Vector{MessageContent}
│ │ ├── TextContent (text::String)
│ │ └── ImageContent (data::String, mime_type::String)
│ └── timestamp::Timestamp (Int64)
├── AssistantMessage (role: "assistant")
│ ├── content::Vector{MessageContent}
│ │ ├── TextContent
│ │ └── ToolCall (type, id, name, arguments::Dict{String, Any})
│ ├── api::String
│ ├── provider::String
│ ├── model::String
│ ├── usage::Usage
│ │ ├── input, output, cache_read, cache_write, total_tokens::Int64
│ │ └── cost::UsageCost (input, output, cache_read, cache_write, total::Float64)
│ ├── stop_reason::String
│ ├── error_message::Union{String, Nothing}
│ └── timestamp::Timestamp
└── ToolResultMessage (role: "toolResult")
├── tool_call_id::String
├── tool_name::String
├── content::Vector{MessageContent}
├── details::Any
├── usage::Union{Usage, Nothing}
├── added_tool_names::Union{Vector{String}, Nothing}
├── is_error::Bool
└── timestamp::Timestamp
AgentMessage (internal, extends Message)
├── UserMessage (same as above)
├── AssistantMessage (same as above)
├── ToolResultMessage (same as above)
├── BashExecutionMessage (custom, converted to UserMessage)
│ ├── role, command, output, exit_code
│ ├── cancelled, truncated, full_output_path
│ └── exclude_from_context::Bool
├── CompactionSummaryMessage (custom, converted to UserMessage)
│ ├── summary, tokens_before, timestamp
└── BranchSummaryMessage (custom, converted to UserMessage)
├── summary, from_id, timestamp
```
### UserMessage ### UserMessage
```julia ```julia
@@ -108,6 +154,16 @@ struct UserMessage <: Message
end end
``` ```
**Usage**:
```julia
# Simple text message
UserMessage(
"user",
[TextContent("Hello, how are you?")],
Int64(Dates.now(Dates.UTC).datetime)
)
```
**Usage**: **Usage**:
```julia ```julia
# Simple text message # Simple text message
@@ -496,9 +552,9 @@ end
**Note**: AgentState is mutable and used internally by Agent **Note**: AgentState is mutable and used internally by Agent
## Key Conversion Functions ## Message Transformation Pipeline
### convertToLlm() ### convertToLlm() - AgentMessage[] → Message[]
```julia ```julia
function convertToLlm(messages::Vector{AgentMessage})::Vector{Message} function convertToLlm(messages::Vector{AgentMessage})::Vector{Message}
@@ -515,26 +571,53 @@ function convertToLlm(messages::Vector{AgentMessage})::Vector{Message}
end end
``` ```
**Purpose**: Transform AgentMessage[] to Message[] for LLM API **Data Flow**:
```
Vector{AgentMessage} (internal conversation history)
│ Type dispatch on convertToLlmMessage():
│ • UserMessage → UserMessage (pass-through)
│ • AssistantMessage → AssistantMessage (pass-through)
│ • ToolResultMessage → ToolResultMessage (pass-through)
│ Custom messages converted to UserMessage:
│ • BashExecutionMessage → UserMessage
│ (via bashExecutionToText() for display)
│ • CompactionSummaryMessage → UserMessage
│ (wrapped with COMPACTION_SUMMARY_PREFIX/SUFFIX)
│ • BranchSummaryMessage → UserMessage
│ (wrapped with BRANCH_SUMMARY_PREFIX/SUFFIX)
Vector{Message} (for LLM API)
- Excludes: BashExecutionMessage (if exclude_from_context)
- Includes: All standard messages + converted custom messages
```
**Example**: **Example**:
```julia ```julia
# Input: AgentMessage[] # Input: Vector{AgentMessage}
[ [
UserMessage(...), UserMessage("user", [TextContent("Hello")], 1234567890),
AssistantMessage(...), AssistantMessage("assistant", [
ToolResultMessage(...), TextContent("Hi there!"),
BashExecutionMessage(...), # Will be converted to UserMessage ToolCall("bash", "call_123", "bash", Dict("command" => "ls"), nothing)
CompactionSummaryMessage(...), # Will be converted to UserMessage ], "openai", "openai", "gpt-4", Usage(...), "done", nothing, 1234567891),
BashExecutionMessage("custom", "ls -la", "file1.md\nfile2.md\n", 0, false, false, nothing, 1234567892, false),
CompactionSummaryMessage("compactionSummary", "Previous conversation compacted", 1000, 1234567893),
] ]
# Output: Message[] # Output: Vector{Message}
[ [
UserMessage(...), UserMessage("user", [TextContent("Hello")], 1234567890),
AssistantMessage(...), AssistantMessage("assistant", [
ToolResultMessage(...), TextContent("Hi there!"),
UserMessage(...), # Converted from BashExecutionMessage ToolCall(...)
UserMessage(...), # Converted from CompactionSummaryMessage ], "openai", "openai", "gpt-4", Usage(...), "done", nothing, 1234567891),
UserMessage("user", [TextContent("Ran `ls -la`\n```\nfile1.md\nfile2.md\n```\n")], 1234567892),
UserMessage("user", [TextContent("<summary>Previous conversation compacted</summary>")], 1234567893),
] ]
``` ```
@@ -571,6 +654,182 @@ function convertToLlmMessage(m::ToolResultMessage)
end end
``` ```
## Complete Data Flow Examples
### Example 1: User Prompt → Assistant Response
```
User Input:
"Hello, what's in the current directory?"
prompt(agent, "Hello, what's in the current directory?")
└─► normalizePromptInput(String)
Input: "Hello, what's in the current directory?"
Output: [UserMessage("user", [TextContent("Hello, what's in the current directory?")], timestamp)]
AgentLoop execution:
├─► transform_context() (optional)
│ Input: [UserMessage(...)]
│ Output: [UserMessage(...)]
├─► convert_to_llm()
│ Input: [UserMessage(...)]
│ Output: [UserMessage(...)]
├─► stream_fn() - LLM API
│ Input: model, Context(...), config
│ Output: AssistantMessage with ToolCall[]
│ role: "assistant"
│ content: [
│ TextContent("I'll check the directory for you."),
│ ToolCall("tool", "tc_123", "bash", Dict("command" => "ls -la"), nothing)
│ ]
│ usage: Usage(input=100, output=20, ...)
│ stop_reason: "done"
├─► executeToolCalls()
│ Input: AssistantMessage with ToolCall[]
│ Output: ToolResultMessage[]
│ role: "toolResult"
│ tool_call_id: "tc_123"
│ tool_name: "bash"
│ content: [TextContent("file1.md\nfile2.md\n")]
│ is_error: false
└─► Append to context.messages
Final Conversation History:
[
UserMessage("user", [TextContent("Hello, what's in the current directory?")], ...),
AssistantMessage("assistant", [
TextContent("I'll check the directory for you."),
ToolCall("tool", "tc_123", "bash", Dict("command" => "ls -la"), nothing)
], "openai", "openai", "gpt-4", Usage(...), "done", nothing, ...),
ToolResultMessage("toolResult", "tc_123", "bash", [TextContent("file1.md\nfile2.md\n")], ..., false, ...),
]
```
### Example 2: Tool Call Execution → Tool Result
```
ToolCall from AssistantMessage
├─ type: "tool"
├─ id: "tc_123"
├─ name: "bash"
├─ arguments: Dict("command" => "ls -la")
└─ partial_json: nothing
prepareToolCall(tool_call)
Finds tool by name "bash"
before_tool_call hook (optional)
Input: BeforeToolCallContext(...)
Output: BeforeToolCallResult(block=false) or nothing
validateToolArguments(tool_call)
Input: Dict("command" => "ls -la")
Output: Dict("command" => "ls -la")
Return: PreparedToolCall("prepared", tool_call, bash_tool, validated_args)
executePreparedToolCall(prepared)
tool.execute("tc_123", Dict("command" => "ls -la"), signal, on_update)
Bash tool executes "ls -la" command
Returns: AgentToolResultMutable(
content: [TextContent("file1.md\nfile2.md\n")],
details: BashToolDetails(...),
usage: nothing,
added_tool_names: nothing,
terminate: nothing
)
finalizeExecutedToolCall(executed)
after_tool_call hook (optional)
Input: AfterToolCallContext(...)
Output: AfterToolCallResult(...) or nothing
Return: FinalizedToolCallOutcome(
tool_call: ToolCall(...),
result: AgentToolResultMutable(...),
is_error: false
)
createToolResultMessage(finalized)
Return: ToolResultMessage(
role: "toolResult",
tool_call_id: "tc_123",
tool_name: "bash",
content: [TextContent("file1.md\nfile2.md\n")],
details: BashToolDetails(...),
usage: nothing,
added_tool_names: nothing,
is_error: false,
timestamp: Int64(...)
)
```
### Example 3: Custom Message Conversion
```
BashExecutionMessage (custom, for logging)
role: "custom"
command: "ls -la"
output: "file1.md\nfile2.md\n"
exit_code: 0
cancelled: false
truncated: false
full_output_path: nothing
timestamp: 1234567890
exclude_from_context: false
convertToLlmMessage(BashExecutionMessage)
bashExecutionToText(msg)
Output: "Ran `ls -la`\n```\nfile1.md\nfile2.md\n```\n"
Return: UserMessage(
"user",
[TextContent("Ran `ls -la`\n```\nfile1.md\nfile2.md\n```\n")],
1234567890
)
(Excluded if exclude_from_context = true)
────────────────────────────────────────────────────────────────────
CompactionSummaryMessage (custom, for history compression)
role: "compactionSummary"
summary: "Previous 100 turns about Python programming"
tokens_before: 15000
timestamp: 1234567890
convertToLlmMessage(CompactionSummaryMessage)
Text = COMPACTION_SUMMARY_PREFIX + summary + COMPACTION_SUMMARY_SUFFIX
Result: "<summary>\nPrevious 100 turns about Python programming\n</summary>"
Return: UserMessage(
"user",
[TextContent("<summary>...\nPrevious 100 turns...\n</summary>")],
1234567890
)
## Summary ## Summary
The type system in AgentCore.jl provides: The type system in AgentCore.jl provides:
+39 -1
View File
@@ -1,6 +1,6 @@
# AgentCore.jl - Session Management Deep Dive # AgentCore.jl - Session Management Deep Dive
## Session Architecture ## Session Architecture with Data Flow
``` ```
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
@@ -20,6 +20,12 @@
│ ▼ ▼ ▼ ▼ ▼ │ │ ▼ ▼ ▼ ▼ ▼ │
│ Message Message Compaction Message BranchSummary │ │ Message Message Compaction Message BranchSummary │
│ │ │ │
│ Data Flow: │
│ AgentMessage[] (AgentState.messages) │
│ │ │
│ └─► appendMessage() → MessageEntry │
│ └─► storage.appendEntry() → JSONL file │
│ │
│ To navigate to E2 (fork point): │ │ To navigate to E2 (fork point): │
│ Session.moveTo(E2) │ │ Session.moveTo(E2) │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
@@ -33,6 +39,38 @@
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
``` ```
## Data Flow: AgentMessage → SessionTreeEntry
```
AgentState.messages::Vector{AgentMessage}
├─► For each message in messages:
│ │
│ ▼
│ ┌──────────────────────────────────────────────────────────────┐
│ │ appendMessage(session, AgentMessage) │
│ │ Input: message::AgentMessage │
│ │ Output: entry_id::String │
│ │ │
│ │ Steps: │
│ │ 1. Create MessageEntry: │
│ │ - type: "message" │
│ │ - id: createEntryId(storage) │
│ │ - parent_id: getLeafId(storage) │
│ │ - timestamp: create_timestamp() │
│ │ - message: copy(message) │
│ │ 2. storage.appendEntry(entry) │
│ │ - Write JSONL line to file │
│ │ - Update leaf_id │
│ │ 3. Return entry.id │
│ └──────────────────────────────────────────────────────────────┘
└─► Entry stored in JSONL:
{"type":"message","id":"msg_1","parent_id":null,"timestamp":"...","message":{...}}
```
## Entry Types
## Entry Types ## Entry Types
```julia ```julia
+84 -53
View File
@@ -1,6 +1,6 @@
# AgentCore.jl - Tools Deep Dive # AgentCore.jl - Tools Deep Dive
## Tool Architecture ## Tool Architecture with Data Flow
``` ```
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
@@ -12,7 +12,7 @@
│ - name: String (identifier) │ │ - name: String (identifier) │
│ - label: String (display name) │ │ - label: String (display name) │
│ - description: String (what it does) │ │ - description: String (what it does) │
│ - parameters: JSON schema │ - parameters::Any (JSON schema or type)
│ - execute::Function (main logic) │ │ - execute::Function (main logic) │
│ - prepare_arguments::Union{Function, Nothing} │ │ - prepare_arguments::Union{Function, Nothing} │
│ - execution_mode::Union{ToolExecutionMode, Nothing} │ │ - execution_mode::Union{ToolExecutionMode, Nothing} │
@@ -21,77 +21,108 @@
┌───────────────┼───────────────┐ ┌───────────────┼───────────────┐
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ BashTool │ │ ReadTool │ │ WriteTool │ │ BashTool │ │ ReadTool │ │ WriteTool │
│ - bash() │ │ - read() │ │ - write() │ │ - bash() │ │ - read() │ │ - write() │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐
│ EditTool │ │ EditTool │
│ - edit() │ │ - edit() │
└─────────────┘ └─────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
│ Tool Execution Flow │ Tool Execution Data Flow
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
Assistant Message Input: AssistantMessage (from LLM)
┌────────────────────────────────────────────────────────┐ content::Vector{MessageContent}
│ AssistantMessage: │ └─ Contains: TextContent[] and ToolCall[]
│ content: [ │
│ TextContent("I'll check the files..."), │
│ ToolCall("bash", {command: "ls -la"}), │ ┌─────────────────────────────────────────────────────────────────────┐
ToolCall("read", {path: "README.md"}) extract ToolCalls
] filter(c -> c isa ToolCall, assistant_message.content)
└────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────
┌────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────
AgentLoop.executeToolCalls() ToolCall Type
- Extract ToolCalls from message content • type::String ("tool")
- Determine execution mode (sequential/parallel) • id::String (unique identifier)
└────────────────────────────────────────────────────────┘ │ • name::String (tool name to execute) │
• arguments::Dict{String, Any} (JSON-like arguments)
├─► executeToolCallsSequential() │ • partial_json::Union{String, Nothing} │
│ (for tools that require order) └─────────────────────────────────────────────────────────────────────┘
└─► executeToolCallsParallel()
(for independent tools)
├─► prepareToolCall() ├─► prepareToolCall()
- before_tool_call hook (optional) Input: tool_call::ToolCall
- validate arguments Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
│ - prepare arguments (optional)
├─► execute() │ Steps:
- Tool-specific logic 1. Find tool by name in context.tools
- Return AgentToolResult 2. before_tool_call hook (optional)
│ Input: BeforeToolCallContext
│ Output: BeforeToolCallResult (block, reason)
│ 3. prepareToolCallArguments() (optional)
│ Input: tool_call.arguments::Dict{String, Any}
│ Output: prepared_arguments::Any
│ 4. validateToolArguments()
│ Input: prepared_tool_call.arguments
│ Output: validated_args::Any
│ 5. Return: PreparedToolCall(kind, tool_call, tool, args)
├─► executePreparedToolCall() (if prepared)
│ Input: PreparedToolCall
│ Output: ExecutedToolCallOutcome
│ tool.execute(tool_call.id, args, signal, on_update)
│ Input: tool_call_id::String
│ args::Any
│ signal::Union{Any, Nothing}
│ on_update::Function (streaming updates)
│ Output: AgentToolResultMutable
│ • content::Vector{MessageContent}
│ • details::Any
│ • usage::Union{Usage, Nothing}
│ • terminate::Union{Bool, Nothing}
├─► finalizeExecutedToolCall() ├─► finalizeExecutedToolCall()
- after_tool_call hook (optional) Input: ExecutedToolCallOutcome
│ Output: FinalizedToolCallOutcome
│ Steps:
│ 1. after_tool_call hook (optional)
│ Input: AfterToolCallContext
│ Output: AfterToolCallResult (patches)
│ 2. Apply patches to result
│ 3. Return: FinalizedToolCallOutcome(tool_call, result, is_error)
└─► createToolResultMessage() └─► createToolResultMessage()
- Emit ToolResultMessage Input: FinalizedToolCallOutcome
Output: ToolResultMessage
• role: "toolResult"
• tool_call_id::String (matches ToolCall.id)
• tool_name::String (matches ToolCall.name)
• content::Vector{MessageContent}
• details::Any
• usage::Union{Usage, Nothing}
• added_tool_names::Union{Vector{String}, Nothing}
• is_error::Bool
• timestamp::Timestamp (Int64)
┌────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────
│ ToolResultMessage │ ToolResultMessage[] (one per ToolCall)
│ - tool_call_id: "ref to original ToolCall" │ └─────────────────────────────────────────────────────────────────────┘
│ - tool_name: "bash" │
│ - content: [TextContent("file1.md\nfile2.md\n")] │
│ - is_error: false │
└────────────────────────────────────────────────────────┘
├─► Append to context.messages (AgentState.messages)
┌────────────────────────────────────────────────────────┐ └─► Next turn: LLM sees tool results as input
│ AgentState.messages.append(tool_result) │
│ - Next turn: LLM sees tool results │
└────────────────────────────────────────────────────────┘
``` ```
## Built-in Tools ## Built-in Tools
## Built-in Tools
### 1. BashTool ### 1. BashTool
```julia ```julia
+186 -12
View File
@@ -165,32 +165,206 @@ AgentStartEvent
└─ AgentEndEvent └─ AgentEndEvent
``` ```
## Data Flow ## Complete Data Flow with Type Transformations
### Message Transformation This documentation shows how data is transformed through the agent lifecycle.
### Message Type Hierarchy
``` ```
AgentMessage[] (internal) Message (for LLM API)
├── UserMessage (role: "user")
│ └── content::Vector{MessageContent}
│ ├── TextContent (text::String)
│ └── ImageContent (data::String, mime_type::String)
├── AssistantMessage (role: "assistant")
│ ├── content::Vector{MessageContent}
│ │ ├── TextContent
│ │ └── ToolCall (id, name, arguments::Dict{String, Any})
│ ├── usage::Usage
│ ├── stop_reason::String
│ └── timestamp::Timestamp
└── ToolResultMessage (role: "toolResult")
├── tool_call_id::String
├── tool_name::String
├── content::Vector{MessageContent}
├── details::Any
├── usage::Union{Usage, Nothing}
├── is_error::Bool
└── timestamp::Timestamp
AgentMessage (internal, extends Message)
├── UserMessage (same as above)
├── AssistantMessage (same as above)
├── ToolResultMessage (same as above)
├── BashExecutionMessage (custom)
│ ├── role, command, output, exit_code
│ ├── cancelled, truncated, exclude_from_context
│ └── timestamp
├── CompactionSummaryMessage (custom)
│ ├── summary, tokens_before, timestamp
│ └── converted to UserMessage for LLM
└── BranchSummaryMessage (custom)
├── summary, from_id, timestamp
└── converted to UserMessage for LLM
```
### Complete Conversation Flow
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 1: User Input (Vector{AgentMessage}) │
└─────────────────────────────────────────────────────────────────────────────┘
prompt(agent, "Hello!")
├─ transform_context() (optional) └─► normalizePromptInput()
Input: "Hello!"::String
AgentMessage[] (transformed) Output: [UserMessage("user", [TextContent("Hello!")], timestamp)]
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 2: AgentLoop Processing │
└─────────────────────────────────────────────────────────────────────────────┘
runAgentLoop()
├─► transform_context() (optional hook)
│ Input: [UserMessage(...)]::Vector{AgentMessage}
│ Output: [UserMessage(...)]::Vector{AgentMessage}
├─► convert_to_llm()
│ Input: [UserMessage(...)]::Vector{AgentMessage}
│ Output: [UserMessage(...)]::Vector{Message}
├─► stream_fn() - LLM API call
│ Input: model, Context(...), config
│ Output: AssistantMessage with ToolCall[]
├─► executeToolCalls()
│ Input: AssistantMessage (with ToolCall[])
│ Output: ToolResultMessage[]
└─► Emit events and append to context.messages
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 3: Final Conversation State │
└─────────────────────────────────────────────────────────────────────────────┘
context.messages::Vector{AgentMessage}
├─ UserMessage("user", [TextContent("Hello!")], ...)
├─ AssistantMessage("assistant", [
│ TextContent("Hi there!"),
│ ToolCall("bash", {...})
│ ], ...)
└─ ToolResultMessage("toolResult", "bash", [TextContent("...")], ...)
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step 4: AgentEndEvent (final output) │
└─────────────────────────────────────────────────────────────────────────────┘
AgentEndEvent(messages::Vector{AgentMessage})
└─ Contains full conversation history
User Input (String / AgentMessage / Vector{AgentMessage})
├─► normalizePromptInput()
│ Input: input::Union{String, AgentMessage, Vector{AgentMessage}}
│ Output: Vector{AgentMessage}
│ • String → UserMessage("user", [TextContent(input)], timestamp)
│ • AgentMessage → [input]
│ • Vector{AgentMessage} → input (pass-through)
├─► prompt(agent, messages)
│ └─► runPromptMessages()
├─ convert_to_llm()
Message[] (LLM API) AgentLoop Execution:
├─► transform_context() (optional hook)
│ Input: context.messages::Vector{AgentMessage}
│ Output: messages::Vector{AgentMessage} (transformed)
├─► convert_to_llm()
│ Input: messages::Vector{AgentMessage}
│ Output: llm_messages::Vector{Message}
│ AgentMessage → Message mapping:
│ • UserMessage → UserMessage (pass-through)
│ • AssistantMessage → AssistantMessage (pass-through)
│ • ToolResultMessage → ToolResultMessage (pass-through)
│ • BashExecutionMessage → UserMessage (text conversion)
│ • CompactionSummaryMessage → UserMessage (text wrapped)
│ • BranchSummaryMessage → UserMessage (text wrapped)
├─► LLM API Call (stream_fn)
│ Input: model, Context(system_prompt, llm_messages, tools), config
│ Output: Stream{AssistantMessageEvent}
├─► AssistantMessage (returned from LLM)
│ content::Vector{MessageContent}
│ └─ Contains: TextContent[] and/or ToolCall[]
├─► executeToolCalls() (if ToolCall[] in content)
│ │
│ ├─► prepareToolCall() for each ToolCall
│ │ Input: tool_call::ToolCall
│ │ Output: PreparedToolCall or ImmediateToolCallOutcome
│ │
│ ├─► executePreparedToolCall() (if prepared)
│ │ Input: PreparedToolCall
│ │ Output: ExecutedToolCallOutcome
│ │ tool.execute() returns AgentToolResultMutable
│ │
│ ├─► finalizeExecutedToolCall()
│ │ Input: ExecutedToolCallOutcome
│ │ Output: FinalizedToolCallOutcome
│ │
│ └─► createToolResultMessage()
│ Input: FinalizedToolCallOutcome
│ Output: ToolResultMessage
│ • role: "toolResult"
│ • tool_call_id, tool_name
│ • content::Vector{MessageContent}
│ • details, usage, added_tool_names
│ • is_error, timestamp
└─► Append to context.messages and new_messages
Vector{AgentMessage} (final conversation history)
Contains: [UserMessage, AssistantMessage, ToolResultMessage, ...]
``` ```
### Tool Execution Flow ### Tool Execution Flow
``` ```
ToolCall (in assistant message) ToolCall (in AssistantMessage.content)
├─ before_tool_call hook (optional)
│ Input: BeforeToolCallContext
│ Output: BeforeToolCallResult (block, reason) or nothing
├─ before_tool_call hook
├─ prepareToolCall() ├─ prepareToolCall()
├─ execute() │ Input: tool_call::ToolCall
├─ after_tool_call hook │ Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
│ • Validates tool exists
│ • Runs before_tool_call hook
│ • Runs prepare_arguments hook (optional)
│ • Runs validateToolArguments (optional)
├─ executePreparedToolCall() (if prepared)
│ Input: PreparedToolCall
│ Output: ExecutedToolCallOutcome
│ tool.execute() returns AgentToolResultMutable
├─ finalizeExecutedToolCall()
│ Input: ExecutedToolCallOutcome
│ Output: FinalizedToolCallOutcome
│ Runs after_tool_call hook (optional)
└─ createToolResultMessage() └─ createToolResultMessage()
Input: FinalizedToolCallOutcome
Output: ToolResultMessage
• role: "toolResult"
• tool_call_id, tool_name
• content::Vector{MessageContent}
• details, usage, added_tool_names
• is_error, timestamp
``` ```
## Best Practices ## Best Practices