diff --git a/learning/01-ARCHITECTURE_OVERVIEW.md b/learning/01-ARCHITECTURE_OVERVIEW.md
index 9f5921b..1d6b82c 100644
--- a/learning/01-ARCHITECTURE_OVERVIEW.md
+++ b/learning/01-ARCHITECTURE_OVERVIEW.md
@@ -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.
diff --git a/learning/02-AGENT_COMPONENT.md b/learning/02-AGENT_COMPONENT.md
index a46a1b8..18cb3f3 100644
--- a/learning/02-AGENT_COMPONENT.md
+++ b/learning/02-AGENT_COMPONENT.md
@@ -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
diff --git a/learning/03-AGENTLOOP_COMPONENT.md b/learning/03-AGENTLOOP_COMPONENT.md
index fa8751c..c4098e3 100644
--- a/learning/03-AGENTLOOP_COMPONENT.md
+++ b/learning/03-AGENTLOOP_COMPONENT.md
@@ -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()
- ▼
-Message[] (LLM API)
+ └─ 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)
+ │
+ ▼
+ 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
diff --git a/learning/04-TYPES_MESSAGES.md b/learning/04-TYPES_MESSAGES.md
index 69c0f3d..3fcdfd5 100644
--- a/learning/04-TYPES_MESSAGES.md
+++ b/learning/04-TYPES_MESSAGES.md
@@ -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("Previous conversation compacted")], 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: "\nPrevious 100 turns about Python programming\n"
+ ↓
+ Return: UserMessage(
+ "user",
+ [TextContent("...\nPrevious 100 turns...\n")],
+ 1234567890
+ )
+
## Summary
The type system in AgentCore.jl provides:
diff --git a/learning/05-SESSION_MANAGEMENT.md b/learning/05-SESSION_MANAGEMENT.md
index db74030..f0a95f1 100644
--- a/learning/05-SESSION_MANAGEMENT.md
+++ b/learning/05-SESSION_MANAGEMENT.md
@@ -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
diff --git a/learning/06-TOOLS.md b/learning/06-TOOLS.md
index dd875ae..1e1f8bc 100644
--- a/learning/06-TOOLS.md
+++ b/learning/06-TOOLS.md
@@ -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} │
@@ -21,77 +21,108 @@
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
- ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
- │ BashTool │ │ ReadTool │ │ WriteTool │
- │ - bash() │ │ - read() │ │ - write() │
- └─────────────┘ └─────────────┘ └─────────────┘
- ┌─────────────┐
- │ EditTool │
- │ - edit() │
- └─────────────┘
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
+ │ BashTool │ │ ReadTool │ │ WriteTool │
+ │ - bash() │ │ - read() │ │ - write() │
+ └─────────────┘ └─────────────┘ └─────────────┘
+ ┌─────────────┐
+ │ EditTool │
+ │ - edit() │
+ └─────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
-│ 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
diff --git a/learning/README.md b/learning/README.md
index e27f844..252edb9 100644
--- a/learning/README.md
+++ b/learning/README.md
@@ -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