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
```
## 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()
- normalizeInput()
└──────────────────────┘
┌────────────────────────────────────────────────────────────────
convertToLlm() - Type Transformation Pipeline
│ 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
- transform_context
└──────────────────────┘
┌──────────────────────────────────────────────────────────────
LLM API Call (stream_fn)
│ 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)
- Context: Message[]
└──────────────────────┘
┌──────────────────────────────────────────────────────────────
executeToolCalls() - Tool Processing
│ Extract: filter(c -> c isa ToolCall, assistant.content) │
│ Output: ExecutedToolCallBatch │
│ • messages: Vector{ToolResultMessage} │
│ • terminate: Bool │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────┐
Response (Streaming)
- Text deltas
- Tool call deltas
└──────────────────────┘
┌──────────────────────────────────────────────────────────────
ToolResultMessage (for each ToolCall)
• 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
- content: Message[]
└──────────────────────┘
┌──────────────────────────────────────────────────────────────
Updated AgentState.messages (AgentMessage[])
│ 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
│ - Extract ToolCalls
│ - Execute tools
└──────────────────────┘
┌──────────────────────┐
│ ToolResultMessage[] │
└──────────────────────┘
┌──────────────────────┐
│ AgentState.messages │ ──► Tool results appended
└──────────────────────┘
│ (Loop back to LLM or end)
┌──────────────────────┐
│ Session Storage │
│ - JSONL format │
│ - Tree entries │
└──────────────────────┘
┌──────────────────────────────────────────────────────────────
Persisted Data (JSON format)
- Each entry has: id, parent_id, timestamp, type
- MessageEntry contains full AgentMessage
└──────────────────────────────────────────────────────────────
```
### 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
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
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.
+24 -5
View File
@@ -243,16 +243,35 @@ followUp(agent, UserMessage(...))
```julia
# Transform messages before sending to LLM
function myConvertToLlm(messages::Vector{AgentMessage})
return filter(
m -> m.role in ["user", "assistant", "toolResult"],
messages
)
function myConvertToLlm(messages::Vector{AgentMessage})::Vector{Message}
result::Vector{Message} = Message[]
for m in messages
converted = convertToLlmMessage(m)
if !isnothing(converted)
push!(result, converted)
end
end
return result
end
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
```julia
+346 -82
View File
@@ -27,11 +27,16 @@ agentLoopContinue()
└─ Returns: EventStream
┌─────────────────────────────────────────────────────────────────────────────┐
Internal Flow
Data Flow with Type Transformations
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 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 │
│ - Appends prompts to context.messages │
│ - Emits AgentStartEvent │
@@ -43,12 +48,20 @@ agentLoopContinue()
┌─────────────────────────────────────────────────────────────────────────────┐
│ 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: │ │
│ │ 1. Get steering/follow-up messages (if any) │ │
│ │ 2. Emit messages as UserMessage │ │
│ │ 3. streamAssistantResponse() │ │
│ │ - Input: context.messages::Vector{AgentMessage} │ │
│ │ - Output: message::AssistantMessage │ │
│ │ 4. Execute tool calls (sequential or parallel) │ │
│ │ - Input: AssistantMessage with ToolCall[] │ │
│ │ - Output: tool_results::Vector{ToolResultMessage} │ │
│ │ 5. Emit TurnEndEvent │ │
│ │ 6. prepare_next_turn (optional) │ │
│ │ 7. should_stop_after_turn? (check termination) │ │
@@ -59,27 +72,89 @@ agentLoopContinue()
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. streamAssistantResponse() ── LLM interaction │
- transform_context (optional)
- convert_to_llm (transform to Message[])
- Call stream_fn (LLM API)
- Stream response deltas
- Emit MessageStart/Update/End events
Input: context::AgentContext
config::AgentLoopConfig
Output: message::AssistantMessage
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 │
│ Input: assistant_message::AssistantMessage (contains ToolCall[]) │
│ current_context::AgentContext │
│ Output: ExecutedToolCallBatch (messages::ToolResultMessage[], terminate) │
│ │
│ For each ToolCall: │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ if EXECUTION_SEQUENTIAL || has_sequential_tool: │ │
│ │ executeToolCallsSequential() │ │
│ │ else: │ │
│ │ executeToolCallsParallel() │ │
│ │ prepareToolCall() │ │
│ │ Input: tool_call::ToolCall │ │
│ │ Output: PreparedToolCall or ImmediateToolCallOutcome │ │
│ │ - 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 │
│ Output: messages::Vector{AgentMessage} │
│ Contains: [UserMessage, AssistantMessage, ToolResultMessage, ...] │
└─────────────────────────────────────────────────────────────────────────────┘
```
@@ -301,17 +376,44 @@ function streamAssistantResponse(
)::AssistantMessage
```
**Flow**:
1. Get messages from context
2. Apply transform_context (optional)
3. Convert to LLM messages with convert_to_llm
4. Create Context object
5. Resolve API key
6. Call stream_fn with model, context, and config
7. Stream events:
- "start" → MessageStartEvent
- "text_start", "text_delta", "text_end" → MessageUpdateEvent
- "done", "error" → MessageEndEvent
**Data Flow**:
```
Input: context.messages::Vector{AgentMessage}
[transform_context] (optional hook)
messages::Vector{AgentMessage}
[convert_to_llm] - Type transformation pipeline
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()
@@ -320,27 +422,56 @@ function executeToolCalls(
current_context::AgentContext,
assistant_message::AssistantMessage,
config::AgentLoopConfig,
signal::Union{Nothing, AbortSignal},
signal::Union{Nothing, AbortSignal>,
emit::AgentEventSink,
)::ExecutedToolCallBatch
```
**Logic**:
```julia
tool_calls = filter(c -> c isa ToolCall, assistant_message.content)
**Data Flow**:
# Check if any tool requires sequential execution
has_sequential = any(tc -> begin
tool = findfirst(t -> t.name == tc.name, current_context.tools)
!isnothing(tool) && tool.execution_mode == EXECUTION_SEQUENTIAL
end, tool_calls)
# Determine execution mode
if config.tool_execution == EXECUTION_SEQUENTIAL || has_sequential
executeToolCallsSequential(...)
else
executeToolCallsParallel(...)
end
```
Input: assistant_message::AssistantMessage
- content::Vector{MessageContent}
└─ Contains ToolCall[] and/or TextContent[]
filter(c -> c isa ToolCall, assistant_message.content)
tool_calls::Vector{ToolCall}
- type: "tool"
- id::String
- name::String
- 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()
@@ -400,29 +531,77 @@ function prepareToolCall(
)::Union{PreparedToolCall, ImmediateToolCallOutcome}
```
**Flow**:
1. Find tool by name
2. If not found → ImmediateToolCallOutcome (error)
3. before_tool_call hook (optional)
4. prepareToolCallArguments() (optional)
5. validateToolArguments()
6. Return PreparedToolCall
**Data Flow**:
```
Input: tool_call::ToolCall
- id::String
- name::String
- 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()
```julia
function executePreparedToolCall(
prepared::PreparedToolCall,
signal::Union{Nothing, AbortSignal},
signal::Union{Nothing, AbortSignal>,
emit::AgentEventSink,
)::ExecutedToolCallOutcome
```
**Flow**:
1. Call tool.execute(id, args, signal, on_update)
2. Collect update events (if any)
3. Wait for all update events
4. Return ExecutedToolCallOutcome(result)
**Data Flow**:
```
Input: prepared::PreparedToolCall
- 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()
@@ -433,13 +612,43 @@ function finalizeExecutedToolCall(
prepared::PreparedToolCall,
executed::ExecutedToolCallOutcome,
config::AgentLoopConfig,
signal::Union{Nothing, AbortSignal},
signal::Union{Nothing, AbortSignal>,
)::FinalizedToolCallOutcome
```
**Flow**:
1. after_tool_call hook (optional)
2. Return FinalizedToolCallOutcome
**Data Flow**:
```
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()
@@ -449,19 +658,39 @@ function createToolResultMessage(
)::ToolResultMessage
```
**Creates**:
```julia
ToolResultMessage(
"toolResult",
finalized.tool_call.id,
finalized.tool_call.name,
finalized.result.content,
finalized.result.details,
finalized.result.usage,
finalized.result.added_tool_names,
finalized.is_error,
timestamp,
)
**Data Flow**:
```
Input: finalized::FinalizedToolCallOutcome
- tool_call::ToolCall
- result::AgentToolResultMutable
- content::Vector{MessageContent}
- details::Any
- usage::Union{Usage, Nothing}
- added_tool_names::Union{Vector{String}, Nothing}
- is_error::Bool
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
@@ -650,35 +879,70 @@ AgentStartEvent
### 1. Message Transformation Pipeline
```
AgentMessage[] (internal)
Vector{AgentMessage} (internal conversation history)
transform_context()
AgentMessage[] (transformed)
├─ transform_context() (optional hook)
│ Input: Vector{AgentMessage}
│ Output: Vector{AgentMessage} (transformed)
└─ convert_to_llm()
│ 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)
│ convert_to_llm()
Message[] (LLM API)
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()
├─ validate arguments
└─ prepare arguments (optional)
Input: tool_call::ToolCall
Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
│ • PreparedToolCall (kind, tool_call, tool, args)
│ • ImmediateToolCallOutcome (immediate, result, is_error)
├─ execute()
├─ Immediate: return result
└─ Prepared: async execution
├─ executePreparedToolCall() (if prepared)
Input: PreparedToolCall
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()
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
+274 -15
View File
@@ -98,6 +98,52 @@
## 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
```julia
@@ -108,6 +154,16 @@ struct UserMessage <: Message
end
```
**Usage**:
```julia
# Simple text message
UserMessage(
"user",
[TextContent("Hello, how are you?")],
Int64(Dates.now(Dates.UTC).datetime)
)
```
**Usage**:
```julia
# Simple text message
@@ -496,9 +552,9 @@ end
**Note**: AgentState is mutable and used internally by Agent
## Key Conversion Functions
## Message Transformation Pipeline
### convertToLlm()
### convertToLlm() - AgentMessage[] → Message[]
```julia
function convertToLlm(messages::Vector{AgentMessage})::Vector{Message}
@@ -515,26 +571,53 @@ function convertToLlm(messages::Vector{AgentMessage})::Vector{Message}
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**:
```julia
# Input: AgentMessage[]
# Input: Vector{AgentMessage}
[
UserMessage(...),
AssistantMessage(...),
ToolResultMessage(...),
BashExecutionMessage(...), # Will be converted to UserMessage
CompactionSummaryMessage(...), # Will be converted to UserMessage
UserMessage("user", [TextContent("Hello")], 1234567890),
AssistantMessage("assistant", [
TextContent("Hi there!"),
ToolCall("bash", "call_123", "bash", Dict("command" => "ls"), nothing)
], "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(...),
AssistantMessage(...),
ToolResultMessage(...),
UserMessage(...), # Converted from BashExecutionMessage
UserMessage(...), # Converted from CompactionSummaryMessage
UserMessage("user", [TextContent("Hello")], 1234567890),
AssistantMessage("assistant", [
TextContent("Hi there!"),
ToolCall(...)
], "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
```
## 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
The type system in AgentCore.jl provides:
+39 -1
View File
@@ -1,6 +1,6 @@
# AgentCore.jl - Session Management Deep Dive
## Session Architecture
## Session Architecture with Data Flow
```
┌─────────────────────────────────────────────────────────────────────────────┐
@@ -20,6 +20,12 @@
│ ▼ ▼ ▼ ▼ ▼ │
│ Message Message Compaction Message BranchSummary │
│ │
│ Data Flow: │
│ AgentMessage[] (AgentState.messages) │
│ │ │
│ └─► appendMessage() → MessageEntry │
│ └─► storage.appendEntry() → JSONL file │
│ │
│ To navigate to E2 (fork point): │
│ 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
```julia
+76 -45
View File
@@ -1,6 +1,6 @@
# AgentCore.jl - Tools Deep Dive
## Tool Architecture
## Tool Architecture with Data Flow
```
┌─────────────────────────────────────────────────────────────────────────────┐
@@ -12,7 +12,7 @@
│ - name: String (identifier) │
│ - label: String (display name) │
│ - description: String (what it does) │
│ - parameters: JSON schema
│ - parameters::Any (JSON schema or type)
│ - execute::Function (main logic) │
│ - prepare_arguments::Union{Function, Nothing} │
│ - execution_mode::Union{ToolExecutionMode, Nothing} │
@@ -31,67 +31,98 @@
└─────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Tool Execution Flow
│ Tool Execution Data Flow
└─────────────────────────────────────────────────────────────────────────────┘
Assistant Message
┌────────────────────────────────────────────────────────┐
│ AssistantMessage: │
│ content: [ │
│ TextContent("I'll check the files..."), │
│ ToolCall("bash", {command: "ls -la"}), │
ToolCall("read", {path: "README.md"})
]
└────────────────────────────────────────────────────────┘
Input: AssistantMessage (from LLM)
content::Vector{MessageContent}
└─ Contains: TextContent[] and ToolCall[]
┌─────────────────────────────────────────────────────────────────────┐
extract ToolCalls
filter(c -> c isa ToolCall, assistant_message.content)
└─────────────────────────────────────────────────────────────────────
┌────────────────────────────────────────────────────────┐
AgentLoop.executeToolCalls()
- Extract ToolCalls from message content
- Determine execution mode (sequential/parallel)
└────────────────────────────────────────────────────────┘
├─► executeToolCallsSequential()
│ (for tools that require order)
└─► executeToolCallsParallel()
(for independent tools)
┌─────────────────────────────────────────────────────────────────────
ToolCall Type
• type::String ("tool")
• id::String (unique identifier)
│ • name::String (tool name to execute) │
• arguments::Dict{String, Any} (JSON-like arguments)
│ • partial_json::Union{String, Nothing} │
└─────────────────────────────────────────────────────────────────────┘
├─► prepareToolCall()
- before_tool_call hook (optional)
- validate arguments
│ - prepare arguments (optional)
Input: tool_call::ToolCall
Output: Union{PreparedToolCall, ImmediateToolCallOutcome}
├─► execute()
- Tool-specific logic
- Return AgentToolResult
│ Steps:
1. Find tool by name in context.tools
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()
- 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()
- 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
│ - tool_call_id: "ref to original ToolCall" │
│ - tool_name: "bash" │
│ - content: [TextContent("file1.md\nfile2.md\n")] │
│ - is_error: false │
└────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────
│ ToolResultMessage[] (one per ToolCall)
└─────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────┐
│ AgentState.messages.append(tool_result) │
│ - Next turn: LLM sees tool results │
└────────────────────────────────────────────────────────┘
├─► Append to context.messages (AgentState.messages)
└─► Next turn: LLM sees tool results as input
```
## Built-in Tools
## Built-in Tools
### 1. BashTool
```julia
+186 -12
View File
@@ -165,32 +165,206 @@ AgentStartEvent
└─ 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)
AgentMessage[] (transformed)
└─► normalizePromptInput()
Input: "Hello!"::String
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
```
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()
├─ execute()
├─ after_tool_call hook
│ Input: tool_call::ToolCall
│ 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()
Input: FinalizedToolCallOutcome
Output: ToolResultMessage
• role: "toolResult"
• tool_call_id, tool_name
• content::Vector{MessageContent}
• details, usage, added_tool_names
• is_error, timestamp
```
## Best Practices